cortex-loop 0.1.0a1__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 (52) hide show
  1. cortex/__init__.py +7 -0
  2. cortex/adapters.py +339 -0
  3. cortex/blocklist.py +51 -0
  4. cortex/challenges.py +210 -0
  5. cortex/cli.py +7 -0
  6. cortex/core.py +601 -0
  7. cortex/core_helpers.py +190 -0
  8. cortex/data/identity_preamble.md +5 -0
  9. cortex/data/layer1_part_a.md +65 -0
  10. cortex/data/layer1_part_b.md +17 -0
  11. cortex/executive.py +295 -0
  12. cortex/foundation.py +185 -0
  13. cortex/genome.py +348 -0
  14. cortex/graveyard.py +226 -0
  15. cortex/hooks/__init__.py +27 -0
  16. cortex/hooks/_shared.py +167 -0
  17. cortex/hooks/post_tool_use.py +13 -0
  18. cortex/hooks/pre_tool_use.py +13 -0
  19. cortex/hooks/session_start.py +13 -0
  20. cortex/hooks/stop.py +13 -0
  21. cortex/invariants.py +258 -0
  22. cortex/packs.py +118 -0
  23. cortex/repomap.py +6 -0
  24. cortex/requirements.py +497 -0
  25. cortex/retry.py +312 -0
  26. cortex/stop_contract.py +217 -0
  27. cortex/stop_payload.py +122 -0
  28. cortex/stop_policy.py +100 -0
  29. cortex/stop_runtime.py +400 -0
  30. cortex/stop_signals.py +75 -0
  31. cortex/store.py +793 -0
  32. cortex/templates/__init__.py +10 -0
  33. cortex/utils.py +58 -0
  34. cortex_loop-0.1.0a1.dist-info/METADATA +121 -0
  35. cortex_loop-0.1.0a1.dist-info/RECORD +52 -0
  36. cortex_loop-0.1.0a1.dist-info/WHEEL +5 -0
  37. cortex_loop-0.1.0a1.dist-info/entry_points.txt +3 -0
  38. cortex_loop-0.1.0a1.dist-info/licenses/LICENSE +21 -0
  39. cortex_loop-0.1.0a1.dist-info/top_level.txt +3 -0
  40. cortex_ops_cli/__init__.py +3 -0
  41. cortex_ops_cli/_adapter_validation.py +119 -0
  42. cortex_ops_cli/_check_report.py +454 -0
  43. cortex_ops_cli/_check_report_output.py +270 -0
  44. cortex_ops_cli/_openai_bridge_probe.py +241 -0
  45. cortex_ops_cli/_openai_bridge_protocol.py +469 -0
  46. cortex_ops_cli/_runtime_profile_templates.py +341 -0
  47. cortex_ops_cli/_runtime_profiles.py +445 -0
  48. cortex_ops_cli/gemini_hooks.py +301 -0
  49. cortex_ops_cli/main.py +911 -0
  50. cortex_ops_cli/openai_app_server_bridge.py +375 -0
  51. cortex_repomap/__init__.py +1 -0
  52. cortex_repomap/engine.py +1201 -0
cortex/stop_policy.py ADDED
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(slots=True)
7
+ class StopVerdict:
8
+ session_status: str
9
+ recommend_revert: bool
10
+ proceed: bool
11
+ feedback_mode: str
12
+
13
+
14
+ def compute_stop_outcome(
15
+ *,
16
+ mode: str,
17
+ fail_on_missing_challenge_coverage: bool,
18
+ fail_on_requirement_audit_gap: bool,
19
+ require_requirement_audit: bool,
20
+ challenge_ok: bool | None,
21
+ invariant_ok: bool | None,
22
+ invariant_recommend_revert: bool,
23
+ missing_challenge_coverage: bool,
24
+ requirements_gate_gap: bool,
25
+ requirement_audit_missing: bool,
26
+ structured_stop_violation: bool,
27
+ loop_detected: bool = False,
28
+ stuck_declared: bool = False,
29
+ ) -> StopVerdict:
30
+ strict_mode = mode == "strict"
31
+ strict_challenge_gate = strict_mode and fail_on_missing_challenge_coverage
32
+ challenge_gate_violation = _challenge_gate_violation(
33
+ strict_challenge_gate=strict_challenge_gate,
34
+ missing_challenge_coverage=missing_challenge_coverage,
35
+ challenge_ok=challenge_ok,
36
+ )
37
+ requirement_audit_violation = _requirement_audit_violation(
38
+ strict_mode=strict_mode,
39
+ fail_on_requirement_audit_gap=fail_on_requirement_audit_gap,
40
+ requirements_gate_gap=requirements_gate_gap,
41
+ requirement_audit_missing=requirement_audit_missing,
42
+ require_requirement_audit=require_requirement_audit,
43
+ )
44
+
45
+ base_status = "completed"
46
+ if invariant_ok is False:
47
+ base_status = "failed_invariants"
48
+ elif structured_stop_violation:
49
+ base_status = "failed_stop_contract"
50
+ elif requirement_audit_violation:
51
+ base_status = "failed_requirements"
52
+ elif challenge_ok is False:
53
+ base_status = "failed_challenges"
54
+ elif missing_challenge_coverage and strict_challenge_gate:
55
+ base_status = "missing_challenge_coverage"
56
+
57
+ if base_status != "completed" and stuck_declared:
58
+ return StopVerdict(
59
+ session_status="stuck",
60
+ recommend_revert=False,
61
+ proceed=False,
62
+ feedback_mode="stuck",
63
+ )
64
+
65
+ recommend_revert = bool(
66
+ invariant_recommend_revert
67
+ or challenge_gate_violation
68
+ or requirement_audit_violation
69
+ or (strict_mode and structured_stop_violation)
70
+ )
71
+ return StopVerdict(
72
+ session_status=base_status,
73
+ recommend_revert=recommend_revert,
74
+ proceed=not recommend_revert,
75
+ feedback_mode="reconsider_approach" if base_status != "completed" and loop_detected else "normal",
76
+ )
77
+
78
+
79
+ def _challenge_gate_violation(
80
+ *,
81
+ strict_challenge_gate: bool,
82
+ missing_challenge_coverage: bool,
83
+ challenge_ok: bool | None,
84
+ ) -> bool:
85
+ return strict_challenge_gate and (missing_challenge_coverage or challenge_ok is False)
86
+
87
+
88
+ def _requirement_audit_violation(
89
+ *,
90
+ strict_mode: bool,
91
+ fail_on_requirement_audit_gap: bool,
92
+ requirements_gate_gap: bool,
93
+ requirement_audit_missing: bool,
94
+ require_requirement_audit: bool,
95
+ ) -> bool:
96
+ return bool(
97
+ strict_mode
98
+ and fail_on_requirement_audit_gap
99
+ and (requirements_gate_gap or (requirement_audit_missing and require_requirement_audit))
100
+ )
cortex/stop_runtime.py ADDED
@@ -0,0 +1,400 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Mapping
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from .challenges import ChallengeEnforcer, ChallengeReport
9
+ from .core_helpers import (
10
+ session_changed_files_since_baseline,
11
+ session_git_snapshot,
12
+ session_metadata,
13
+ session_required_requirement_ids,
14
+ session_witness_context,
15
+ )
16
+ from .genome import CortexGenome
17
+ from .graveyard import Graveyard
18
+ from .invariants import InvariantReport, InvariantRunner
19
+ from .requirements import (
20
+ RequirementAuditEvaluation,
21
+ TruthClaimsEvaluation,
22
+ evaluate_requirement_audit_payload,
23
+ evaluate_truth_claims_payload,
24
+ )
25
+ from .stop_contract import (
26
+ StopContract,
27
+ reconcile_required_requirement_ids,
28
+ structured_stop_contract_diagnostic,
29
+ )
30
+ from .stop_policy import compute_stop_outcome
31
+ from .stop_signals import build_stop_attempt_signature, stop_attempt_similarity
32
+ from .store import SQLiteStore
33
+ from .utils import _as_bool, _as_string_list
34
+
35
+ LOOP_WARNING = (
36
+ "Stop attempt is highly similar to the previous failed Stop; reconsider the approach "
37
+ "instead of refining the same attempt."
38
+ )
39
+
40
+
41
+ @dataclass(slots=True)
42
+ class StopPathOutcome:
43
+ warnings: list[str]
44
+ challenge_report: ChallengeReport | None
45
+ challenge_diagnostics: list[dict[str, Any]]
46
+ missing_challenge_coverage: bool
47
+ invariant_report: InvariantReport | None
48
+ requirement_audit: RequirementAuditEvaluation
49
+ truth_claims: TruthClaimsEvaluation
50
+ required_requirement_ids: list[str]
51
+ required_requirement_ids_source: str
52
+ contract_diagnostic: dict[str, Any] | None
53
+ git_snapshot: dict[str, Any] | None
54
+ stuck_declaration: dict[str, Any] | None
55
+ structured_stop_violation: bool
56
+ strict_message_fallback_violation: bool
57
+ enforcement_pass: bool
58
+ session_status: str
59
+ recommend_revert: bool
60
+ proceed: bool
61
+ feedback_mode: str
62
+ stop_attempt_signature: dict[str, Any]
63
+ loop_detected: bool
64
+ loop_similarity: float | None
65
+
66
+
67
+ class StopPathRunner:
68
+ def __init__(
69
+ self,
70
+ *,
71
+ root: Path,
72
+ store: SQLiteStore,
73
+ genome: CortexGenome,
74
+ challenges: ChallengeEnforcer,
75
+ invariants: InvariantRunner,
76
+ graveyard: Graveyard,
77
+ session_metadata_loader: Callable[[SQLiteStore, str], dict[str, Any]] = session_metadata,
78
+ session_git_snapshotter: Callable[[Path], dict[str, Any]] = session_git_snapshot,
79
+ session_changed_files_since_baseline_fn: Callable[
80
+ ..., tuple[list[str] | None, str | None]
81
+ ] = session_changed_files_since_baseline,
82
+ session_required_requirement_ids_loader: Callable[
83
+ [SQLiteStore, str], list[str]
84
+ ] = session_required_requirement_ids,
85
+ session_witness_context_loader: Callable[
86
+ [SQLiteStore, str], Mapping[str, list[str]]
87
+ ] = session_witness_context,
88
+ ) -> None:
89
+ self.root = root
90
+ self.store = store
91
+ self.genome = genome
92
+ self.challenges = challenges
93
+ self.invariants = invariants
94
+ self.graveyard = graveyard
95
+ self._session_metadata = session_metadata_loader
96
+ self._session_git_snapshot = session_git_snapshotter
97
+ self._session_changed_files_since_baseline = session_changed_files_since_baseline_fn
98
+ self._session_required_requirement_ids = session_required_requirement_ids_loader
99
+ self._session_witness_context = session_witness_context_loader
100
+
101
+ def run(
102
+ self,
103
+ *,
104
+ session_id: str,
105
+ payload: Mapping[str, Any],
106
+ stop_contract: StopContract,
107
+ ) -> StopPathOutcome:
108
+ session_meta = self._session_metadata(self.store, session_id)
109
+ warnings = list(stop_contract.warnings)
110
+ baseline_git_snapshot = (
111
+ dict(session_meta.get("git_snapshot"))
112
+ if isinstance(session_meta.get("git_snapshot"), Mapping)
113
+ else None
114
+ )
115
+ strict_message_fallback_violation = (
116
+ self.genome.hooks.mode == "strict" and stop_contract.stop_source == "message_fallback"
117
+ )
118
+ contract_diagnostic = stop_contract.contract_diagnostic
119
+ if strict_message_fallback_violation:
120
+ warnings.append(
121
+ "Strict mode rejects Stop message-fallback payloads; send stop fields natively or via payload.stop_fields."
122
+ )
123
+ if contract_diagnostic is None:
124
+ contract_diagnostic = structured_stop_contract_diagnostic(
125
+ "strict_message_fallback_rejected"
126
+ )
127
+
128
+ coverage_payload = stop_contract.challenge_coverage
129
+ challenge_report: ChallengeReport | None = None
130
+ challenge_diagnostics: list[dict[str, Any]] = []
131
+ missing_challenge_coverage = False
132
+ require_verifiable_challenge_coverage = (
133
+ self.genome.hooks.mode == "strict"
134
+ and self.genome.hooks.fail_on_missing_challenge_coverage
135
+ )
136
+ needs_witness = bool(
137
+ (require_verifiable_challenge_coverage and isinstance(coverage_payload, Mapping))
138
+ or stop_contract.requirement_audit is not None
139
+ or stop_contract.truth_claims is not None
140
+ )
141
+ witness = (
142
+ self._session_witness_context(self.store, session_id)
143
+ if needs_witness
144
+ else {"commands": [], "tools": []}
145
+ )
146
+ if isinstance(coverage_payload, Mapping):
147
+ challenge_report = self.challenges.evaluate(
148
+ session_id=session_id,
149
+ coverage_payload=coverage_payload,
150
+ require_verifiable_coverage=require_verifiable_challenge_coverage,
151
+ root=self.root,
152
+ witness=witness,
153
+ )
154
+ if not challenge_report.ok:
155
+ warnings.append(
156
+ "Missing challenge coverage for categories: "
157
+ + ", ".join(challenge_report.missing_categories)
158
+ )
159
+ warnings.extend(challenge_report.config_warnings)
160
+ challenge_diagnostics = list(challenge_report.diagnostics)
161
+ elif coverage_payload is not None:
162
+ missing_challenge_coverage = self.genome.challenges.require_coverage
163
+ warnings.append(
164
+ "Invalid challenge_coverage format; expected an object mapping category names to values."
165
+ )
166
+ challenge_diagnostics = self.challenges.invalid_coverage_diagnostics(coverage_payload)
167
+ elif self.genome.challenges.require_coverage:
168
+ missing_challenge_coverage = True
169
+ message = (
170
+ "No challenge_coverage provided in Stop payload; skipping challenge gate recording. "
171
+ "Include challenge_coverage directly or via payload.stop_fields"
172
+ )
173
+ if self.genome.hooks.allow_message_stop_fallback:
174
+ message += ", or as a STOP_FIELDS_JSON trailer"
175
+ warnings.append(message + ".")
176
+ challenge_diagnostics = self.challenges.missing_coverage_diagnostics()
177
+
178
+ required_requirement_ids, requirement_ids_source, required_ids_warning = (
179
+ reconcile_required_requirement_ids(
180
+ self._session_required_requirement_ids(self.store, session_id),
181
+ list(stop_contract.required_requirement_ids),
182
+ )
183
+ )
184
+ if required_ids_warning:
185
+ warnings.append(required_ids_warning)
186
+
187
+ truth_claims_payload = (
188
+ stop_contract.truth_claims if isinstance(stop_contract.truth_claims, Mapping) else None
189
+ )
190
+ has_modified_files_claim = bool(
191
+ truth_claims_payload and _as_string_list(truth_claims_payload.get("modified_files"))
192
+ )
193
+ observed_modified_files: list[str] | None = None
194
+ modified_files_error: str | None = None
195
+ if has_modified_files_claim:
196
+ current_git_snapshot = self._session_git_snapshot(self.root)
197
+ observed_modified_files, modified_files_error = self._session_changed_files_since_baseline(
198
+ baseline_snapshot=baseline_git_snapshot,
199
+ current_snapshot=current_git_snapshot,
200
+ )
201
+
202
+ requirement_audit = evaluate_requirement_audit_payload(
203
+ stop_contract.requirement_audit,
204
+ require_requirement_audit=self.genome.hooks.require_requirement_audit,
205
+ require_evidence_for_passed_requirement=self.genome.hooks.require_evidence_for_passed_requirement,
206
+ required_requirement_ids=required_requirement_ids,
207
+ root=self.root,
208
+ witness=witness,
209
+ )
210
+ warnings.extend(requirement_audit.warnings)
211
+ truth_claims = evaluate_truth_claims_payload(
212
+ stop_contract.truth_claims,
213
+ root=self.root,
214
+ witness=witness,
215
+ observed_modified_files=observed_modified_files,
216
+ modified_files_error=modified_files_error,
217
+ )
218
+ warnings.extend(truth_claims.warnings)
219
+
220
+ invariant_report = None
221
+ if self.genome.invariants.run_on_stop and _as_bool(payload.get("run_invariants"), True):
222
+ invariant_report = self.invariants.run(
223
+ session_id=session_id,
224
+ extra_pytest_args=_as_string_list(payload.get("pytest_args")),
225
+ )
226
+ if not invariant_report.ok:
227
+ warnings.append("Invariant suite reported failures.")
228
+
229
+ if stop_contract.failed_approach:
230
+ self.graveyard.record_failure(
231
+ session_id=session_id,
232
+ summary=str(stop_contract.failed_approach["summary"]),
233
+ reason=str(stop_contract.failed_approach["reason"]),
234
+ files=_as_string_list(stop_contract.failed_approach.get("files")),
235
+ )
236
+
237
+ structured_stop_violation = (
238
+ stop_contract.structured_stop_violation or strict_message_fallback_violation
239
+ )
240
+ challenge_ok = None if challenge_report is None else challenge_report.ok
241
+ invariant_ok = None if invariant_report is None else invariant_report.ok
242
+ requirements_gate_gap = requirement_audit.gap or truth_claims.gap
243
+ stop_attempt_signature = build_stop_attempt_signature(
244
+ challenge_coverage=coverage_payload if isinstance(coverage_payload, Mapping) else None,
245
+ witness=witness,
246
+ truth_claims_payload=truth_claims_payload,
247
+ failed_approach=stop_contract.failed_approach,
248
+ observed_modified_files=observed_modified_files,
249
+ )
250
+ previous_signature = session_meta.get("stop_attempt_signature")
251
+ loop_similarity = stop_attempt_similarity(
252
+ previous_signature if isinstance(previous_signature, Mapping) else None,
253
+ stop_attempt_signature,
254
+ )
255
+ loop_detected = bool(loop_similarity is not None and loop_similarity >= 0.85)
256
+ verdict = compute_stop_outcome(
257
+ mode=self.genome.hooks.mode,
258
+ fail_on_missing_challenge_coverage=self.genome.hooks.fail_on_missing_challenge_coverage,
259
+ fail_on_requirement_audit_gap=self.genome.hooks.fail_on_requirement_audit_gap,
260
+ require_requirement_audit=self.genome.hooks.require_requirement_audit,
261
+ challenge_ok=challenge_ok,
262
+ invariant_ok=invariant_ok,
263
+ invariant_recommend_revert=bool(invariant_report and invariant_report.recommend_revert),
264
+ missing_challenge_coverage=missing_challenge_coverage,
265
+ requirements_gate_gap=requirements_gate_gap,
266
+ requirement_audit_missing=requirement_audit.missing,
267
+ structured_stop_violation=structured_stop_violation,
268
+ loop_detected=loop_detected,
269
+ stuck_declared=stop_contract.stuck_declaration is not None,
270
+ )
271
+ if loop_detected and verdict.session_status != "completed":
272
+ warnings.append(LOOP_WARNING)
273
+ enforcement_pass = verdict.session_status == "completed"
274
+
275
+ return StopPathOutcome(
276
+ warnings=warnings,
277
+ challenge_report=challenge_report,
278
+ challenge_diagnostics=challenge_diagnostics,
279
+ missing_challenge_coverage=missing_challenge_coverage,
280
+ invariant_report=invariant_report,
281
+ requirement_audit=requirement_audit,
282
+ truth_claims=truth_claims,
283
+ required_requirement_ids=required_requirement_ids,
284
+ required_requirement_ids_source=requirement_ids_source,
285
+ contract_diagnostic=contract_diagnostic,
286
+ git_snapshot=baseline_git_snapshot,
287
+ stuck_declaration=stop_contract.stuck_declaration,
288
+ structured_stop_violation=structured_stop_violation,
289
+ strict_message_fallback_violation=strict_message_fallback_violation,
290
+ enforcement_pass=enforcement_pass,
291
+ session_status=verdict.session_status,
292
+ recommend_revert=verdict.recommend_revert,
293
+ proceed=verdict.proceed,
294
+ feedback_mode=verdict.feedback_mode,
295
+ stop_attempt_signature=stop_attempt_signature,
296
+ loop_detected=loop_detected,
297
+ loop_similarity=loop_similarity,
298
+ )
299
+
300
+ @staticmethod
301
+ def close_session_metadata(
302
+ *,
303
+ outcome: StopPathOutcome,
304
+ stop_contract: StopContract,
305
+ executive_signature: str | None,
306
+ executive_record: dict[str, Any] | None,
307
+ ) -> dict[str, Any]:
308
+ challenge_report = outcome.challenge_report
309
+ invariant_report = outcome.invariant_report
310
+ requirement_audit = outcome.requirement_audit
311
+ truth_claims = outcome.truth_claims
312
+ requirement_audit_gap = requirement_audit.gap
313
+ truth_claims_gap = truth_claims.gap
314
+ requirements_gate_gap = requirement_audit_gap or truth_claims_gap
315
+ challenge_ok = None if challenge_report is None else challenge_report.ok
316
+ invariant_ok = None if invariant_report is None else invariant_report.ok
317
+ return {
318
+ "hook": "Stop",
319
+ "enforcement_pass": outcome.enforcement_pass,
320
+ "challenge_ok": challenge_ok,
321
+ "challenge_diagnostics": outcome.challenge_diagnostics,
322
+ "challenge_coverage_missing": outcome.missing_challenge_coverage,
323
+ "challenge_unverified_categories": (
324
+ [] if challenge_report is None else challenge_report.unverified_categories
325
+ ),
326
+ "challenge_uncheckable_categories": (
327
+ [] if challenge_report is None else challenge_report.uncheckable_categories
328
+ ),
329
+ "invariant_ok": invariant_ok,
330
+ "requirement_audit_ok": (
331
+ None if requirement_audit.details is None else requirement_audit.details["ok"]
332
+ ),
333
+ "requirement_audit_diagnostics": requirement_audit.diagnostics,
334
+ "requirement_audit_missing": requirement_audit.missing,
335
+ "requirement_audit_gap": requirement_audit_gap,
336
+ "truth_claims_ok": None if truth_claims.report is None else truth_claims.report["ok"],
337
+ "truth_claims_diagnostics": truth_claims.diagnostics,
338
+ "truth_claims_gap": truth_claims_gap,
339
+ "requirements_gate_gap": requirements_gate_gap,
340
+ "required_requirement_ids": outcome.required_requirement_ids,
341
+ "required_requirement_ids_source": outcome.required_requirement_ids_source,
342
+ "contract_diagnostic": outcome.contract_diagnostic,
343
+ "git_snapshot": outcome.git_snapshot,
344
+ "stuck_declared": outcome.stuck_declaration is not None,
345
+ "stuck_declaration": outcome.stuck_declaration,
346
+ "structured_stop_violation": outcome.structured_stop_violation,
347
+ "stop_source": stop_contract.stop_source,
348
+ "stop_fields_source": stop_contract.stop_fields_source,
349
+ "stop_fields_fallback_used": stop_contract.stop_fields_fallback_used,
350
+ "stop_key_normalization_count": stop_contract.stop_key_normalization_count,
351
+ "strict_message_fallback_violation": outcome.strict_message_fallback_violation,
352
+ "feedback_mode": outcome.feedback_mode,
353
+ "stop_attempt_signature": outcome.stop_attempt_signature,
354
+ "loop_detected": outcome.loop_detected,
355
+ "loop_similarity": outcome.loop_similarity,
356
+ "executive_last_stop_signature": executive_signature,
357
+ "executive_memory_recorded": bool(executive_record),
358
+ "executive_memory_record_id": None if executive_record is None else executive_record["id"],
359
+ }
360
+
361
+ @staticmethod
362
+ def response_payload(
363
+ *,
364
+ outcome: StopPathOutcome,
365
+ stop_contract: StopContract,
366
+ ) -> dict[str, Any]:
367
+ challenge_report = outcome.challenge_report
368
+ requirement_audit = outcome.requirement_audit
369
+ truth_claims = outcome.truth_claims
370
+ return {
371
+ "session_status": outcome.session_status,
372
+ "challenge_report": None if challenge_report is None else challenge_report.to_dict(),
373
+ "challenge_diagnostics": outcome.challenge_diagnostics,
374
+ "challenge_coverage_missing": outcome.missing_challenge_coverage,
375
+ "invariant_report": None if outcome.invariant_report is None else outcome.invariant_report.to_dict(),
376
+ "requirement_audit_report": requirement_audit.report,
377
+ "requirement_audit_diagnostics": requirement_audit.diagnostics,
378
+ "truth_claims_report": truth_claims.report,
379
+ "truth_claims_diagnostics": truth_claims.diagnostics,
380
+ "required_requirement_ids": outcome.required_requirement_ids,
381
+ "requirement_audit_missing": requirement_audit.missing,
382
+ "requirement_audit_gap": requirement_audit.gap,
383
+ "truth_claims_gap": truth_claims.gap,
384
+ "requirements_gate_gap": requirement_audit.gap or truth_claims.gap,
385
+ "enforcement_pass": outcome.enforcement_pass,
386
+ "contract_diagnostic": outcome.contract_diagnostic,
387
+ "stuck_declared": outcome.stuck_declaration is not None,
388
+ "stuck_declaration": outcome.stuck_declaration,
389
+ "structured_stop_violation": outcome.structured_stop_violation,
390
+ "stop_source": stop_contract.stop_source,
391
+ "stop_fields_source": stop_contract.stop_fields_source,
392
+ "stop_fields_fallback_used": stop_contract.stop_fields_fallback_used,
393
+ "stop_key_normalization_count": stop_contract.stop_key_normalization_count,
394
+ "feedback_mode": outcome.feedback_mode,
395
+ "stop_attempt_signature": outcome.stop_attempt_signature,
396
+ "loop_detected": outcome.loop_detected,
397
+ "loop_similarity": outcome.loop_similarity,
398
+ "recommend_revert": outcome.recommend_revert,
399
+ "proceed": outcome.proceed,
400
+ }
cortex/stop_signals.py ADDED
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+ from .utils import _as_string_list
7
+
8
+
9
+ def build_stop_attempt_signature(
10
+ *,
11
+ challenge_coverage: Mapping[str, Any] | None,
12
+ witness: Mapping[str, list[str]] | None,
13
+ truth_claims_payload: Mapping[str, Any] | None,
14
+ failed_approach: Mapping[str, Any] | None,
15
+ observed_modified_files: list[str] | None,
16
+ ) -> dict[str, Any]:
17
+ challenge_shape: list[str] = []
18
+ if isinstance(challenge_coverage, Mapping):
19
+ for category in sorted(str(key).strip() for key in challenge_coverage.keys()):
20
+ raw = challenge_coverage.get(category)
21
+ if isinstance(raw, Mapping):
22
+ challenge_shape.append(f"{category}:covered={bool(raw.get('covered', False))}")
23
+ else:
24
+ challenge_shape.append(f"{category}:covered={bool(raw)}")
25
+
26
+ witnessed_commands = sorted(
27
+ {
28
+ normalize_stop_command_signal(command)
29
+ for command in _as_string_list((witness or {}).get("commands"))
30
+ if normalize_stop_command_signal(command)
31
+ }
32
+ )
33
+ file_signal = sorted(
34
+ {
35
+ path
36
+ for path in (
37
+ _as_string_list((truth_claims_payload or {}).get("modified_files"))
38
+ + _as_string_list((failed_approach or {}).get("files"))
39
+ + _as_string_list(observed_modified_files)
40
+ )
41
+ if path
42
+ }
43
+ )
44
+ return {
45
+ "challenge_shape": challenge_shape,
46
+ "witnessed_commands": witnessed_commands,
47
+ "file_signal": file_signal,
48
+ }
49
+
50
+
51
+ def stop_attempt_similarity(previous: Mapping[str, Any] | None, current: Mapping[str, Any]) -> float | None:
52
+ if not isinstance(previous, Mapping):
53
+ return None
54
+ scores = [
55
+ _signal_jaccard(previous.get("challenge_shape"), current.get("challenge_shape")),
56
+ _signal_jaccard(previous.get("witnessed_commands"), current.get("witnessed_commands")),
57
+ _signal_jaccard(previous.get("file_signal"), current.get("file_signal")),
58
+ ]
59
+ present_scores = [score for score in scores if score is not None]
60
+ return round(sum(present_scores) / len(present_scores), 3) if present_scores else None
61
+
62
+
63
+ def normalize_stop_command_signal(command: str) -> str:
64
+ return " ".join(str(command).strip().lower().split())
65
+
66
+
67
+ def _signal_jaccard(left: Any, right: Any) -> float | None:
68
+ left_set = set(_as_string_list(left))
69
+ right_set = set(_as_string_list(right))
70
+ if not left_set and not right_set:
71
+ return None
72
+ union = left_set | right_set
73
+ if not union:
74
+ return None
75
+ return len(left_set & right_set) / len(union)