cortex-loop 0.1.0a1__tar.gz → 0.1.0a2__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 (133) hide show
  1. {cortex_loop-0.1.0a1/cortex_loop.egg-info → cortex_loop-0.1.0a2}/PKG-INFO +36 -13
  2. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/README.md +35 -12
  3. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/__init__.py +1 -1
  4. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/adapters.py +60 -57
  5. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/challenges.py +49 -5
  6. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/core.py +579 -9
  7. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/hooks/_shared.py +129 -2
  8. cortex_loop-0.1.0a2/cortex/hooks/instructions_loaded.py +40 -0
  9. cortex_loop-0.1.0a2/cortex/hooks/post_tool_use_failure.py +13 -0
  10. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/requirements.py +42 -1
  11. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/stop_contract.py +32 -5
  12. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/stop_policy.py +30 -3
  13. cortex_loop-0.1.0a2/cortex/stop_runtime.py +1433 -0
  14. cortex_loop-0.1.0a2/cortex/stop_signals.py +333 -0
  15. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2/cortex_loop.egg-info}/PKG-INFO +36 -13
  16. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_loop.egg-info/SOURCES.txt +18 -0
  17. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_ops_cli/_adapter_validation.py +20 -1
  18. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_ops_cli/_check_report.py +15 -8
  19. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_ops_cli/_openai_bridge_protocol.py +179 -133
  20. cortex_loop-0.1.0a2/cortex_ops_cli/_runtime_profile_templates.py +447 -0
  21. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_ops_cli/_runtime_profiles.py +144 -38
  22. cortex_loop-0.1.0a2/cortex_ops_cli/gemini_hooks.py +582 -0
  23. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_ops_cli/main.py +10 -3
  24. cortex_loop-0.1.0a2/cortex_ops_cli/openai_app_server_bridge.py +963 -0
  25. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/pyproject.toml +1 -1
  26. cortex_loop-0.1.0a2/tests/test_adapter_implementation_dossier.py +69 -0
  27. cortex_loop-0.1.0a2/tests/test_adapter_validation_contract.py +258 -0
  28. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_adapters.py +79 -14
  29. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_challenges.py +3 -0
  30. cortex_loop-0.1.0a2/tests/test_claude_boundedness_postmortem.py +57 -0
  31. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_cli.py +366 -7
  32. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_context_budget_regression.py +34 -0
  33. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_core.py +950 -7
  34. cortex_loop-0.1.0a2/tests/test_cortex_state_analysis.py +75 -0
  35. cortex_loop-0.1.0a2/tests/test_docs_hygiene.py +760 -0
  36. cortex_loop-0.1.0a2/tests/test_dossier_snapshots.py +129 -0
  37. cortex_loop-0.1.0a2/tests/test_gemini_hooks_bridge.py +1518 -0
  38. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_hooks.py +286 -1
  39. cortex_loop-0.1.0a2/tests/test_model_kernel_adapter_audit.py +60 -0
  40. cortex_loop-0.1.0a2/tests/test_net_positive_benchmark_result_table.py +133 -0
  41. cortex_loop-0.1.0a2/tests/test_net_positive_benchmark_scorecard.py +137 -0
  42. cortex_loop-0.1.0a2/tests/test_net_positive_benchmark_task_manifest.py +116 -0
  43. cortex_loop-0.1.0a2/tests/test_net_positive_claude_post_split_baseline.py +126 -0
  44. cortex_loop-0.1.0a2/tests/test_net_positive_gemini_post_split_baseline.py +165 -0
  45. cortex_loop-0.1.0a2/tests/test_net_positive_openai_post_split_baseline.py +235 -0
  46. cortex_loop-0.1.0a2/tests/test_net_positive_phase1_baseline_blocker.py +113 -0
  47. cortex_loop-0.1.0a2/tests/test_net_positive_phase9_current_packet.py +251 -0
  48. cortex_loop-0.1.0a2/tests/test_net_positive_phase9_openai_assisted_current_pair.py +106 -0
  49. cortex_loop-0.1.0a2/tests/test_net_positive_phase9_rerun_readiness.py +183 -0
  50. cortex_loop-0.1.0a2/tests/test_openai_app_server_bridge.py +2297 -0
  51. cortex_loop-0.1.0a2/tests/test_release_notes.py +37 -0
  52. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_repo_hygiene.py +3 -0
  53. cortex_loop-0.1.0a2/tests/test_repo_hygiene_sessions.py +398 -0
  54. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_requirements.py +22 -1
  55. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_runtime_latency_regression.py +3 -1
  56. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_stop_contract.py +29 -4
  57. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_stop_policy.py +37 -2
  58. cortex_loop-0.1.0a2/tests/test_stop_runtime.py +1476 -0
  59. cortex_loop-0.1.0a2/tests/test_stop_signals.py +412 -0
  60. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_workflow_contract.py +53 -1
  61. cortex_loop-0.1.0a1/cortex/stop_runtime.py +0 -400
  62. cortex_loop-0.1.0a1/cortex/stop_signals.py +0 -75
  63. cortex_loop-0.1.0a1/cortex_ops_cli/_runtime_profile_templates.py +0 -341
  64. cortex_loop-0.1.0a1/cortex_ops_cli/gemini_hooks.py +0 -301
  65. cortex_loop-0.1.0a1/cortex_ops_cli/openai_app_server_bridge.py +0 -375
  66. cortex_loop-0.1.0a1/tests/test_adapter_validation_contract.py +0 -127
  67. cortex_loop-0.1.0a1/tests/test_docs_hygiene.py +0 -443
  68. cortex_loop-0.1.0a1/tests/test_gemini_hooks_bridge.py +0 -570
  69. cortex_loop-0.1.0a1/tests/test_openai_app_server_bridge.py +0 -850
  70. cortex_loop-0.1.0a1/tests/test_repo_hygiene_sessions.py +0 -174
  71. cortex_loop-0.1.0a1/tests/test_stop_runtime.py +0 -145
  72. cortex_loop-0.1.0a1/tests/test_stop_signals.py +0 -44
  73. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/LICENSE +0 -0
  74. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/MANIFEST.in +0 -0
  75. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/blocklist.py +0 -0
  76. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/cli.py +0 -0
  77. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/core_helpers.py +0 -0
  78. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/data/identity_preamble.md +0 -0
  79. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/data/layer1_part_a.md +0 -0
  80. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/data/layer1_part_b.md +0 -0
  81. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/executive.py +0 -0
  82. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/foundation.py +0 -0
  83. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/genome.py +0 -0
  84. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/graveyard.py +0 -0
  85. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/hooks/__init__.py +0 -0
  86. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/hooks/post_tool_use.py +0 -0
  87. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/hooks/pre_tool_use.py +0 -0
  88. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/hooks/session_start.py +0 -0
  89. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/hooks/stop.py +0 -0
  90. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/invariants.py +0 -0
  91. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/packs.py +0 -0
  92. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/repomap.py +0 -0
  93. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/retry.py +0 -0
  94. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/stop_payload.py +0 -0
  95. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/store.py +0 -0
  96. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/templates/__init__.py +0 -0
  97. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex/utils.py +0 -0
  98. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_loop.egg-info/dependency_links.txt +0 -0
  99. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_loop.egg-info/entry_points.txt +0 -0
  100. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_loop.egg-info/requires.txt +0 -0
  101. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_loop.egg-info/top_level.txt +0 -0
  102. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_ops_cli/__init__.py +0 -0
  103. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_ops_cli/_check_report_output.py +0 -0
  104. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_ops_cli/_openai_bridge_probe.py +0 -0
  105. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_repomap/__init__.py +0 -0
  106. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/cortex_repomap/engine.py +0 -0
  107. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/setup.cfg +0 -0
  108. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_adapter_validation_fixture_hygiene.py +0 -0
  109. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_adapter_validation_fixture_provenance.py +0 -0
  110. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_blocklist.py +0 -0
  111. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_core_helpers.py +0 -0
  112. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_executive.py +0 -0
  113. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_executive_layer2.py +0 -0
  114. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_foundation.py +0 -0
  115. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_gemini_fixture_hygiene.py +0 -0
  116. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_gemini_fixture_provenance.py +0 -0
  117. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_genome.py +0 -0
  118. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_graveyard.py +0 -0
  119. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_install_surfaces.py +0 -0
  120. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_integration.py +0 -0
  121. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_invariants.py +0 -0
  122. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_kernel_density_targets.py +0 -0
  123. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_kernel_runtime_agnostic_guard.py +0 -0
  124. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_mini_openai_ab.py +0 -0
  125. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_packs.py +0 -0
  126. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_preflight_openai_probe_v121.py +0 -0
  127. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_repomap.py +0 -0
  128. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_retry.py +0 -0
  129. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_shims.py +0 -0
  130. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_stop_payload.py +0 -0
  131. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_store.py +0 -0
  132. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_structure_boundaries.py +0 -0
  133. {cortex_loop-0.1.0a1 → cortex_loop-0.1.0a2}/tests/test_study_scripts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cortex-loop
3
- Version: 0.1.0a1
3
+ Version: 0.1.0a2
4
4
  Summary: Runtime-agnostic enforcement layer for AI coding agents
5
5
  Author: Cortex contributors
6
6
  License-Expression: MIT
@@ -46,7 +46,8 @@ The stop path is the product. Cortex is not a planning framework, generic memory
46
46
  | --- | --- | --- | --- |
47
47
  | Claude Code | `cortex runtime install --profile claude` | Shipped | Strongest current runtime. The truthful boundary is live-proven; remaining caveats are minor. |
48
48
  | Gemini CLI | `cortex runtime install --profile gemini` | Shipped with watchlist | The truthful boundary is live-proven. One operational watchlist remains: blocked malformed stops can leave Gemini CLI resident until operator termination. |
49
- | OpenAI Codex App Server | `cortex runtime install --profile openai` | Experimental | Several critical paths are proven on the latest stable surface, but positive strict close is still not dependable enough for broader support. |
49
+ | OpenAI Codex App Server (native) | `cortex runtime install --profile openai` | Experimental | Honest native App Server surface: one turn, then stop audit. Several critical paths are proven, but positive strict close is still not dependable enough for broader support. |
50
+ | OpenAI Codex App Server (assisted) | `cortex runtime install --profile openai-assisted` | Experimental | Explicit bounded assisted mode with a current committed shared-harness pair: Cortex provides session-start context with a short kernel-owned completion preview, one short evidence-expectation line, and one kernel-derived corrective turn. The lane remains supplemental-only, not native-substitutive proof. |
50
51
 
51
52
  Status labels in this table:
52
53
  - `Shipped`: live-proven on the shared harness; remaining caveats are non-critical.
@@ -55,23 +56,36 @@ Status labels in this table:
55
56
 
56
57
  Detailed runtime release evidence lives in [docs/ADAPTER_VALIDATION.md](docs/ADAPTER_VALIDATION.md) plus committed provenance under [tests/fixtures/adapter_validation/claude/PROVENANCE.json](tests/fixtures/adapter_validation/claude/PROVENANCE.json), [tests/fixtures/adapter_validation/gemini/PROVENANCE.json](tests/fixtures/adapter_validation/gemini/PROVENANCE.json), and [tests/fixtures/adapter_validation/openai/PROVENANCE.json](tests/fixtures/adapter_validation/openai/PROVENANCE.json).
57
58
 
59
+ Current product-proof truth: Cortex is strong on truthful completion boundaries,
60
+ but the repo still does not earn a net-positive product claim over the raw
61
+ model. Phase 9 is landed at a truthful-withheld endpoint rather than an earned
62
+ product-proof win.
63
+
64
+ This repo state is also the final archival v1 reference point for the
65
+ enforcement-layer line. The archival package target is `cortex-loop 0.1.0a2`.
66
+
58
67
  ## Quickstart
59
68
 
60
- As of 2026-03-07, `cortex-loop` is not yet published on PyPI. Until live
61
- package publication is proven, the truthful first-run path is from a repo
62
- checkout:
69
+ Default public install:
63
70
 
64
71
  ```bash
65
- python3 -m venv .venv
66
- . .venv/bin/activate
67
- python -m pip install . pytest
72
+ pipx install cortex-loop
68
73
  cortex init
69
74
  cortex runtime install --profile claude
70
75
  ```
71
76
 
72
- Before `cortex check`, choose the baseline that matches the repository:
77
+ Supported advanced alternative:
78
+
79
+ ```bash
80
+ uv tool install cortex-loop
81
+ ```
82
+
83
+ Before `cortex check`, choose the baseline that matches the repository or the
84
+ machine you are using:
73
85
 
74
- - trusted local repo: switch `cortex.toml` to the trusted-host baseline in
86
+ - trusted local repo without Docker: add `pytest` to the promoted tool surface
87
+ (`pipx inject cortex-loop pytest` or `uv tool install --with pytest
88
+ cortex-loop`) and switch `cortex.toml` to the trusted-host baseline in
75
89
  [docs/SECURE_DEFAULTS.md](docs/SECURE_DEFAULTS.md)
76
90
  - untrusted or container baseline: keep the defaults and ensure Docker is on
77
91
  `PATH`
@@ -82,12 +96,21 @@ Then run:
82
96
  cortex check
83
97
  ```
84
98
 
85
- The staged public install posture, update and uninstall commands, and the PyPI
86
- publication gate live in [docs/INSTALL.md](docs/INSTALL.md).
99
+ Install, update, uninstall, and live proof details live in
100
+ [docs/INSTALL.md](docs/INSTALL.md).
101
+ Guided evaluator onboarding lives in
102
+ [docs/ALPHA_TESTER_ONBOARDING.md](docs/ALPHA_TESTER_ONBOARDING.md).
103
+ Trust boundary and pack review posture live in
104
+ [docs/TRUST_BOUNDARY.md](docs/TRUST_BOUNDARY.md) and
105
+ [docs/PACK_POLICY.md](docs/PACK_POLICY.md).
87
106
 
88
107
  For a first evaluation, keep `claude`. Use `gemini` when you want the shipped
89
108
  watchlist surface. Use `openai` only when you are explicitly evaluating the
90
- experimental surface.
109
+ experimental native surface. Use `openai-assisted` when you want the current
110
+ bounded assisted OpenAI lane: session-start context with a short kernel-owned
111
+ completion preview, one short evidence-expectation line, one kernel-derived
112
+ corrective turn aimed at the unresolved completion gap, and a current
113
+ shared-harness pair that still remains supplemental-only.
91
114
 
92
115
  ## What Cortex Enforces
93
116
 
@@ -14,7 +14,8 @@ The stop path is the product. Cortex is not a planning framework, generic memory
14
14
  | --- | --- | --- | --- |
15
15
  | Claude Code | `cortex runtime install --profile claude` | Shipped | Strongest current runtime. The truthful boundary is live-proven; remaining caveats are minor. |
16
16
  | Gemini CLI | `cortex runtime install --profile gemini` | Shipped with watchlist | The truthful boundary is live-proven. One operational watchlist remains: blocked malformed stops can leave Gemini CLI resident until operator termination. |
17
- | OpenAI Codex App Server | `cortex runtime install --profile openai` | Experimental | Several critical paths are proven on the latest stable surface, but positive strict close is still not dependable enough for broader support. |
17
+ | OpenAI Codex App Server (native) | `cortex runtime install --profile openai` | Experimental | Honest native App Server surface: one turn, then stop audit. Several critical paths are proven, but positive strict close is still not dependable enough for broader support. |
18
+ | OpenAI Codex App Server (assisted) | `cortex runtime install --profile openai-assisted` | Experimental | Explicit bounded assisted mode with a current committed shared-harness pair: Cortex provides session-start context with a short kernel-owned completion preview, one short evidence-expectation line, and one kernel-derived corrective turn. The lane remains supplemental-only, not native-substitutive proof. |
18
19
 
19
20
  Status labels in this table:
20
21
  - `Shipped`: live-proven on the shared harness; remaining caveats are non-critical.
@@ -23,23 +24,36 @@ Status labels in this table:
23
24
 
24
25
  Detailed runtime release evidence lives in [docs/ADAPTER_VALIDATION.md](docs/ADAPTER_VALIDATION.md) plus committed provenance under [tests/fixtures/adapter_validation/claude/PROVENANCE.json](tests/fixtures/adapter_validation/claude/PROVENANCE.json), [tests/fixtures/adapter_validation/gemini/PROVENANCE.json](tests/fixtures/adapter_validation/gemini/PROVENANCE.json), and [tests/fixtures/adapter_validation/openai/PROVENANCE.json](tests/fixtures/adapter_validation/openai/PROVENANCE.json).
25
26
 
27
+ Current product-proof truth: Cortex is strong on truthful completion boundaries,
28
+ but the repo still does not earn a net-positive product claim over the raw
29
+ model. Phase 9 is landed at a truthful-withheld endpoint rather than an earned
30
+ product-proof win.
31
+
32
+ This repo state is also the final archival v1 reference point for the
33
+ enforcement-layer line. The archival package target is `cortex-loop 0.1.0a2`.
34
+
26
35
  ## Quickstart
27
36
 
28
- As of 2026-03-07, `cortex-loop` is not yet published on PyPI. Until live
29
- package publication is proven, the truthful first-run path is from a repo
30
- checkout:
37
+ Default public install:
31
38
 
32
39
  ```bash
33
- python3 -m venv .venv
34
- . .venv/bin/activate
35
- python -m pip install . pytest
40
+ pipx install cortex-loop
36
41
  cortex init
37
42
  cortex runtime install --profile claude
38
43
  ```
39
44
 
40
- Before `cortex check`, choose the baseline that matches the repository:
45
+ Supported advanced alternative:
46
+
47
+ ```bash
48
+ uv tool install cortex-loop
49
+ ```
50
+
51
+ Before `cortex check`, choose the baseline that matches the repository or the
52
+ machine you are using:
41
53
 
42
- - trusted local repo: switch `cortex.toml` to the trusted-host baseline in
54
+ - trusted local repo without Docker: add `pytest` to the promoted tool surface
55
+ (`pipx inject cortex-loop pytest` or `uv tool install --with pytest
56
+ cortex-loop`) and switch `cortex.toml` to the trusted-host baseline in
43
57
  [docs/SECURE_DEFAULTS.md](docs/SECURE_DEFAULTS.md)
44
58
  - untrusted or container baseline: keep the defaults and ensure Docker is on
45
59
  `PATH`
@@ -50,12 +64,21 @@ Then run:
50
64
  cortex check
51
65
  ```
52
66
 
53
- The staged public install posture, update and uninstall commands, and the PyPI
54
- publication gate live in [docs/INSTALL.md](docs/INSTALL.md).
67
+ Install, update, uninstall, and live proof details live in
68
+ [docs/INSTALL.md](docs/INSTALL.md).
69
+ Guided evaluator onboarding lives in
70
+ [docs/ALPHA_TESTER_ONBOARDING.md](docs/ALPHA_TESTER_ONBOARDING.md).
71
+ Trust boundary and pack review posture live in
72
+ [docs/TRUST_BOUNDARY.md](docs/TRUST_BOUNDARY.md) and
73
+ [docs/PACK_POLICY.md](docs/PACK_POLICY.md).
55
74
 
56
75
  For a first evaluation, keep `claude`. Use `gemini` when you want the shipped
57
76
  watchlist surface. Use `openai` only when you are explicitly evaluating the
58
- experimental surface.
77
+ experimental native surface. Use `openai-assisted` when you want the current
78
+ bounded assisted OpenAI lane: session-start context with a short kernel-owned
79
+ completion preview, one short evidence-expectation line, one kernel-derived
80
+ corrective turn aimed at the unresolved completion gap, and a current
81
+ shared-harness pair that still remains supplemental-only.
59
82
 
60
83
  ## What Cortex Enforces
61
84
 
@@ -4,4 +4,4 @@ from .core import CortexKernel
4
4
  from .hooks import marker
5
5
 
6
6
  __all__ = ["CortexKernel", "marker"]
7
- __version__ = "0.1.0a1"
7
+ __version__ = "0.1.0a2"
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import importlib
4
- import json
5
4
  import logging
6
5
  import re
7
6
  from collections.abc import Mapping
@@ -32,6 +31,8 @@ CANONICAL_EVENT_ALIASES = {
32
31
  "pretooluse": "pre_tool_use",
33
32
  "post_tool_use": "post_tool_use",
34
33
  "posttooluse": "post_tool_use",
34
+ "post_tool_use_failure": "post_tool_use",
35
+ "posttoolusefailure": "post_tool_use",
35
36
  "stop": "stop",
36
37
  }
37
38
 
@@ -51,7 +52,7 @@ class ClaudeAdapter:
51
52
  def normalize(self, event_name: str, payload: Mapping[str, Any] | None = None) -> NormalizedEvent:
52
53
  name = _normalize_event_name(event_name, self.EVENT_ALIASES)
53
54
  data = dict(payload) if isinstance(payload, Mapping) else {}
54
- data = _normalize_claude_payload(data)
55
+ data = _normalize_claude_payload(data, normalized_event_name=name)
55
56
  message = data.get("last_assistant_message")
56
57
  if isinstance(message, str):
57
58
  rewritten = _rewrite_legacy_trailer_markers(message)
@@ -94,6 +95,7 @@ class GeminiAdapter:
94
95
  _normalize_session_id(data)
95
96
  if name in {"pre_tool_use", "post_tool_use"}:
96
97
  _normalize_tool_name(data, candidate_keys=("tool_name",))
98
+ _normalize_gemini_file_targets(data)
97
99
  if name == "post_tool_use":
98
100
  _normalize_gemini_status(data)
99
101
  if name == "stop":
@@ -107,8 +109,13 @@ class GeminiAdapter:
107
109
  type(prompt_response).__name__,
108
110
  )
109
111
  prompt_response = ""
110
- stop_fields, passthrough = _normalize_gemini_stop_fields(prompt_response)
111
- data["stop_fields"] = stop_fields
112
+ stop_fields, passthrough, parse_error = _normalize_gemini_stop_fields(prompt_response)
113
+ if isinstance(stop_fields, dict):
114
+ data["stop_fields"] = stop_fields
115
+ else:
116
+ data.pop("stop_fields", None)
117
+ if parse_error:
118
+ data["stop_fields_parse_error"] = parse_error
112
119
  data["last_assistant_message"] = passthrough
113
120
  return NormalizedEvent(name=name, payload=data)
114
121
 
@@ -193,14 +200,46 @@ def _normalize_event_name(event_name: str, aliases: dict[str, str]) -> str:
193
200
  return aliases.get(token) or aliases.get(token.replace("_", "")) or token
194
201
 
195
202
 
196
- def _normalize_claude_payload(payload: dict[str, Any]) -> dict[str, Any]:
203
+ def _normalize_claude_payload(payload: dict[str, Any], *, normalized_event_name: str) -> dict[str, Any]:
197
204
  _normalize_tool_name(payload, candidate_keys=("tool_name", "tool", "toolName", "action"))
198
205
  _normalize_session_id(payload)
206
+ _normalize_claude_file_targets(payload)
207
+ if normalized_event_name == "session_start":
208
+ payload.setdefault("runtime_mode", "native_preview")
209
+ payload.setdefault("stop_trailer_marker", "STOP_FIELDS_JSON")
210
+ if normalized_event_name == "post_tool_use":
211
+ _normalize_claude_post_tool_status(payload)
199
212
  if "stop_fields" not in payload and "cortex_stop" in payload:
200
213
  payload["stop_fields"] = payload.get("cortex_stop")
201
214
  return payload
202
215
 
203
216
 
217
+ def _normalize_claude_post_tool_status(payload: dict[str, Any]) -> None:
218
+ raw_hook = str(payload.get("hook_event_name") or "").strip()
219
+ if raw_hook == "PostToolUseFailure":
220
+ payload.setdefault("status", "error")
221
+ tool_response = payload.get("tool_response")
222
+ if isinstance(tool_response, Mapping):
223
+ response_error = tool_response.get("error")
224
+ if payload.get("status") is None and response_error:
225
+ payload["status"] = "error"
226
+ if payload.get("error") is None and isinstance(response_error, str) and response_error.strip():
227
+ payload["error"] = response_error.strip()
228
+
229
+
230
+ def _normalize_claude_file_targets(payload: dict[str, Any]) -> None:
231
+ tool_input = payload.get("tool_input")
232
+ if not isinstance(tool_input, Mapping):
233
+ return
234
+ file_path = str(tool_input.get("file_path") or tool_input.get("path") or "").strip()
235
+ if not file_path:
236
+ return
237
+ payload.setdefault("target_files", [file_path])
238
+ tool_name = str(payload.get("tool_name") or "").strip().lower()
239
+ if tool_name in {"edit", "multiedit", "write", "notebookedit"}:
240
+ payload.setdefault("planned_files", [file_path])
241
+
242
+
204
243
  def _normalize_claude_stop_fields(message: str) -> tuple[dict[str, Any] | None, str]:
205
244
  parsed, _, _ = parse_stop_fields_json(message)
206
245
  passthrough = _strip_gemini_stop_markers(message)
@@ -255,7 +294,20 @@ def _normalize_gemini_status(payload: dict[str, Any]) -> None:
255
294
  payload["status"] = "ok"
256
295
 
257
296
 
258
- def _normalize_gemini_stop_fields(prompt_response: str) -> tuple[dict[str, Any], str]:
297
+ def _normalize_gemini_file_targets(payload: dict[str, Any]) -> None:
298
+ tool_input = payload.get("tool_input")
299
+ if not isinstance(tool_input, Mapping):
300
+ return
301
+ file_path = str(tool_input.get("file_path") or tool_input.get("path") or "").strip()
302
+ if not file_path:
303
+ return
304
+ payload.setdefault("target_files", [file_path])
305
+ tool_name = str(payload.get("tool_name") or "").strip().lower()
306
+ if tool_name in {"replace", "write_file", "edit", "write"}:
307
+ payload.setdefault("planned_files", [file_path])
308
+
309
+
310
+ def _normalize_gemini_stop_fields(prompt_response: str) -> tuple[dict[str, Any] | None, str, str]:
259
311
  parsed, marker_found, error = parse_stop_fields_json(prompt_response)
260
312
  passthrough = _strip_gemini_stop_markers(prompt_response)
261
313
 
@@ -263,16 +315,9 @@ def _normalize_gemini_stop_fields(prompt_response: str) -> tuple[dict[str, Any],
263
315
  stop_fields = {str(k): v for k, v in parsed.items()}
264
316
  if passthrough and not stop_fields.get("summary"):
265
317
  stop_fields["summary"] = passthrough
266
- return stop_fields, passthrough
318
+ return stop_fields, passthrough, ""
267
319
 
268
- stop_fields = _recover_partial_stop_fields(prompt_response)
269
- if marker_found and error:
270
- stop_fields["marker_parse_error"] = error
271
- if passthrough:
272
- stop_fields.setdefault("summary", passthrough)
273
- elif prompt_response.strip():
274
- stop_fields.setdefault("summary", prompt_response.strip())
275
- return stop_fields, passthrough
320
+ return None, passthrough or prompt_response.strip(), str(error or "") if marker_found else ""
276
321
 
277
322
 
278
323
  def _strip_gemini_stop_markers(text: str) -> str:
@@ -289,48 +334,6 @@ def _strip_gemini_stop_markers(text: str) -> str:
289
334
  cleaned = cleaned[:marker_idx]
290
335
  return cleaned.strip()
291
336
 
292
-
293
- def _recover_partial_stop_fields(text: str) -> dict[str, Any]:
294
- recovered: dict[str, Any] = {}
295
-
296
- coverage: dict[str, bool] = {}
297
- for key in ("null_inputs", "boundary_values", "error_handling", "graveyard_regression"):
298
- match = re.search(rf'"{key}"\s*:\s*(true|false)', text, flags=re.IGNORECASE)
299
- if match:
300
- coverage[key] = match.group(1).lower() == "true"
301
- if coverage:
302
- recovered["challenge_coverage"] = coverage
303
-
304
- truth_claims: dict[str, list[str]] = {}
305
- for key in ("modified_files", "tests_ran"):
306
- values = _recover_string_list(text, key)
307
- if values:
308
- truth_claims[key] = values
309
- if truth_claims:
310
- recovered["truth_claims"] = truth_claims
311
-
312
- return recovered
313
-
314
-
315
- def _recover_string_list(text: str, key: str) -> list[str]:
316
- match = re.search(rf'"{key}"\s*:\s*\[(.*?)\]', text, flags=re.DOTALL | re.IGNORECASE)
317
- if not match:
318
- return []
319
- values: list[str] = []
320
- seen: set[str] = set()
321
- for token in re.findall(r'"((?:\\.|[^"\\])*)"', match.group(1)):
322
- try:
323
- value = str(json.loads(f'"{token}"'))
324
- except json.JSONDecodeError:
325
- value = token
326
- cleaned = value.strip()
327
- if not cleaned or cleaned in seen:
328
- continue
329
- seen.add(cleaned)
330
- values.append(cleaned)
331
- return values
332
-
333
-
334
337
  def _rewrite_legacy_trailer_markers(message: str) -> str:
335
338
  return (
336
339
  message.replace("CORTEX_STOP_JSON:", "STOP_FIELDS_JSON:")
@@ -33,6 +33,7 @@ class ChallengeReport:
33
33
  diagnostics: list[dict[str, Any]]
34
34
  config_warnings: list[str]
35
35
  ok: bool
36
+ gap_entries: list[str] = field(default_factory=list)
36
37
 
37
38
  def to_dict(self) -> dict[str, Any]:
38
39
  return {
@@ -45,6 +46,7 @@ class ChallengeReport:
45
46
  "diagnostics": self.diagnostics,
46
47
  "config_warnings": self.config_warnings,
47
48
  "ok": self.ok,
49
+ "gap_entries": self.gap_entries,
48
50
  }
49
51
 
50
52
 
@@ -67,6 +69,7 @@ class ChallengeEnforcer:
67
69
  missing: list[str] = []
68
70
  unverified: list[str] = []
69
71
  uncheckable: list[str] = []
72
+ gap_entries: list[str] = []
70
73
  diagnostics: list[dict[str, Any]] = []
71
74
  config_warnings: list[str] = []
72
75
  resolved_root = root.resolve() if root is not None else None
@@ -82,6 +85,7 @@ class ChallengeEnforcer:
82
85
  for category in self.config.active_categories:
83
86
  raw = coverage_payload.get(category)
84
87
  covered, evidence = self._coerce_coverage(raw)
88
+ gap_kind = "missing" if raw is None else "uncovered"
85
89
  if require_verifiable_coverage and covered:
86
90
  verification = self._verify_covered_category(
87
91
  evidence=evidence,
@@ -95,13 +99,16 @@ class ChallengeEnforcer:
95
99
  reason = str(verification.get("reason") or "missing verifiable evidence")
96
100
  if verification_status == "uncheckable":
97
101
  uncheckable.append(category)
102
+ gap_kind = "uncheckable"
98
103
  else:
99
104
  unverified.append(category)
105
+ gap_kind = "unverified"
100
106
  config_warnings.append(
101
107
  f"Challenge coverage '{category}' marked covered but evidence is {verification_status}: {reason}"
102
108
  )
103
109
  if not covered:
104
110
  missing.append(category)
111
+ gap_entries.append(f"{category}:{gap_kind}")
105
112
  diagnostics.append(
106
113
  self._coverage_diagnostic(
107
114
  category=category,
@@ -125,16 +132,31 @@ class ChallengeEnforcer:
125
132
  diagnostics=diagnostics,
126
133
  config_warnings=config_warnings,
127
134
  ok=ok,
135
+ gap_entries=sorted(set(gap_entries)),
128
136
  )
129
137
 
130
138
  def missing_coverage_diagnostics(self) -> list[dict[str, Any]]:
131
139
  return [
132
- {"evidence_found": [], "evidence_expected": [f"challenge_coverage for: {', '.join(self.config.active_categories)}"], "gap_description": "No challenge_coverage was provided for the stop attempt.", "gap_characterization": "comprehension_gap", "distance_signal": "far"}
140
+ {
141
+ "evidence_found": [],
142
+ "evidence_expected": [f"challenge_coverage for: {', '.join(self.config.active_categories)}"],
143
+ "gap_description": "No challenge_coverage was provided for the stop attempt.",
144
+ "gap_characterization": "comprehension_gap",
145
+ "distance_signal": "far",
146
+ "gap_entries": ["__all__:missing"],
147
+ }
133
148
  ]
134
149
 
135
150
  def invalid_coverage_diagnostics(self, raw: Any) -> list[dict[str, Any]]:
136
151
  return [
137
- {"evidence_found": [f"challenge_coverage={type(raw).__name__}"], "evidence_expected": ["challenge_coverage object keyed by active challenge categories"], "gap_description": "Challenge coverage used an invalid payload shape.", "gap_characterization": "comprehension_gap", "distance_signal": "far"}
152
+ {
153
+ "evidence_found": [f"challenge_coverage={type(raw).__name__}"],
154
+ "evidence_expected": ["challenge_coverage object keyed by active challenge categories"],
155
+ "gap_description": "Challenge coverage used an invalid payload shape.",
156
+ "gap_characterization": "comprehension_gap",
157
+ "distance_signal": "far",
158
+ "gap_entries": ["__all__:invalid_shape"],
159
+ }
138
160
  ]
139
161
 
140
162
  @staticmethod
@@ -200,11 +222,33 @@ class ChallengeEnforcer:
200
222
  ) -> dict[str, Any]:
201
223
  verification = evidence.get("verification") if isinstance(evidence, Mapping) else None
202
224
  if raw is None:
203
- return {"evidence_found": [], "evidence_expected": [f"challenge_coverage.{category}=true"], "gap_description": f"Challenge category '{category}' was not addressed in stop coverage.", "gap_characterization": "comprehension_gap", "distance_signal": "far"}
225
+ return {
226
+ "evidence_found": [],
227
+ "evidence_expected": [f"challenge_coverage.{category}=true"],
228
+ "gap_description": f"Challenge category '{category}' was not addressed in stop coverage.",
229
+ "gap_characterization": "comprehension_gap",
230
+ "distance_signal": "far",
231
+ "gap_entries": [f"{category}:missing"],
232
+ }
204
233
 
205
234
  evidence_refs = _as_string_list(evidence.get("evidence")) if isinstance(evidence, Mapping) else []
206
235
  if require_verifiable_coverage and isinstance(verification, Mapping):
207
236
  status = str(verification.get("status") or "unverified")
208
- return {"evidence_found": evidence_refs or [f"verification_status={status}"], "evidence_expected": [f"verifiable evidence for challenge '{category}'"], "gap_description": f"Challenge category '{category}' was claimed but not verifiably supported.", "gap_characterization": "execution_gap", "distance_signal": "moderate" if evidence_refs else "far"}
237
+ gap_kind = "uncheckable" if status == "uncheckable" else "unverified"
238
+ return {
239
+ "evidence_found": evidence_refs or [f"verification_status={status}"],
240
+ "evidence_expected": [f"verifiable evidence for challenge '{category}'"],
241
+ "gap_description": f"Challenge category '{category}' was claimed but not verifiably supported.",
242
+ "gap_characterization": "execution_gap",
243
+ "distance_signal": "moderate" if evidence_refs else "far",
244
+ "gap_entries": [f"{category}:{gap_kind}"],
245
+ }
209
246
 
210
- return {"evidence_found": ["covered=false"], "evidence_expected": [f"challenge_coverage.{category}=true"], "gap_description": f"Challenge category '{category}' remains uncovered.", "gap_characterization": "comprehension_gap", "distance_signal": "moderate"}
247
+ return {
248
+ "evidence_found": ["covered=false"],
249
+ "evidence_expected": [f"challenge_coverage.{category}=true"],
250
+ "gap_description": f"Challenge category '{category}' remains uncovered.",
251
+ "gap_characterization": "comprehension_gap",
252
+ "distance_signal": "moderate",
253
+ "gap_entries": [f"{category}:uncovered"],
254
+ }