@arbiterforge/ca-pi 0.6.0

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 (206) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +558 -0
  3. package/package.json +35 -0
  4. package/plugins/ca-pi/CHANGELOG.md +1030 -0
  5. package/plugins/ca-pi/COMMANDS.md +90 -0
  6. package/plugins/ca-pi/ORCHESTRATOR.md +159 -0
  7. package/plugins/ca-pi/SKILLS.md +47 -0
  8. package/plugins/ca-pi/SPRINT.md +142 -0
  9. package/plugins/ca-pi/agents/INDEX.md +31 -0
  10. package/plugins/ca-pi/agents/architecture-drift-reviewer.md +86 -0
  11. package/plugins/ca-pi/agents/auth-crypto-reviewer.md +60 -0
  12. package/plugins/ca-pi/agents/backend-author.md +60 -0
  13. package/plugins/ca-pi/agents/checkpoint-aggregator.md +111 -0
  14. package/plugins/ca-pi/agents/coverage-auditor.md +71 -0
  15. package/plugins/ca-pi/agents/decision-challenger.md +116 -0
  16. package/plugins/ca-pi/agents/dependency-reviewer.md +79 -0
  17. package/plugins/ca-pi/agents/design-quality-reviewer.md +80 -0
  18. package/plugins/ca-pi/agents/finding-triage.md +86 -0
  19. package/plugins/ca-pi/agents/frontend-author.md +64 -0
  20. package/plugins/ca-pi/agents/grader.md +173 -0
  21. package/plugins/ca-pi/agents/infra-author.md +64 -0
  22. package/plugins/ca-pi/agents/map-deps.md +35 -0
  23. package/plugins/ca-pi/agents/map-structure.md +37 -0
  24. package/plugins/ca-pi/agents/migration-reviewer.md +65 -0
  25. package/plugins/ca-pi/agents/scout.md +127 -0
  26. package/plugins/ca-pi/agents/security-reviewer.md +72 -0
  27. package/plugins/ca-pi/agents/tribunal-lens-reviewer.md +65 -0
  28. package/plugins/ca-pi/extensions/codearbiter-child.js +1885 -0
  29. package/plugins/ca-pi/extensions/codearbiter.js +9802 -0
  30. package/plugins/ca-pi/generated/command-catalog.json +197 -0
  31. package/plugins/ca-pi/generated/roles.json +213 -0
  32. package/plugins/ca-pi/helpers/windows-supervisor.js +205 -0
  33. package/plugins/ca-pi/hooks/_activationlib.py +196 -0
  34. package/plugins/ca-pi/hooks/_arbiterstatelib.py +208 -0
  35. package/plugins/ca-pi/hooks/_babysitlib.py +76 -0
  36. package/plugins/ca-pi/hooks/_bashguardlib.py +1667 -0
  37. package/plugins/ca-pi/hooks/_boxlib.py +131 -0
  38. package/plugins/ca-pi/hooks/_colorlib.py +304 -0
  39. package/plugins/ca-pi/hooks/_durabilitylib.py +186 -0
  40. package/plugins/ca-pi/hooks/_entrylib.py +41 -0
  41. package/plugins/ca-pi/hooks/_fmtlib.py +161 -0
  42. package/plugins/ca-pi/hooks/_gitexec.py +45 -0
  43. package/plugins/ca-pi/hooks/_githooks.py +920 -0
  44. package/plugins/ca-pi/hooks/_gitlib.py +110 -0
  45. package/plugins/ca-pi/hooks/_hooklib.py +595 -0
  46. package/plugins/ca-pi/hooks/_host.py +115 -0
  47. package/plugins/ca-pi/hooks/_intentlib.py +242 -0
  48. package/plugins/ca-pi/hooks/_ledgerlib.py +1035 -0
  49. package/plugins/ca-pi/hooks/_metricslib.py +709 -0
  50. package/plugins/ca-pi/hooks/_pathnorm.py +74 -0
  51. package/plugins/ca-pi/hooks/_planfilelib.py +664 -0
  52. package/plugins/ca-pi/hooks/_previewlib.py +193 -0
  53. package/plugins/ca-pi/hooks/_protectedlib.py +312 -0
  54. package/plugins/ca-pi/hooks/_protectedstatelib.py +411 -0
  55. package/plugins/ca-pi/hooks/_provenancelib.py +971 -0
  56. package/plugins/ca-pi/hooks/_prunelib.py +1398 -0
  57. package/plugins/ca-pi/hooks/_prunepolicy.py +235 -0
  58. package/plugins/ca-pi/hooks/_readinjectlib.py +1080 -0
  59. package/plugins/ca-pi/hooks/_releaselib.py +2657 -0
  60. package/plugins/ca-pi/hooks/_scopelib.py +262 -0
  61. package/plugins/ca-pi/hooks/_segmentslib.py +278 -0
  62. package/plugins/ca-pi/hooks/_sensitivelib.py +270 -0
  63. package/plugins/ca-pi/hooks/_sessionlib.py +78 -0
  64. package/plugins/ca-pi/hooks/_sloplib.py +244 -0
  65. package/plugins/ca-pi/hooks/_standuplib.py +214 -0
  66. package/plugins/ca-pi/hooks/_subagentslib.py +219 -0
  67. package/plugins/ca-pi/hooks/_taskboardlib.py +1088 -0
  68. package/plugins/ca-pi/hooks/_updatelib.py +278 -0
  69. package/plugins/ca-pi/hooks/babysit.py +47 -0
  70. package/plugins/ca-pi/hooks/boardsync.py +129 -0
  71. package/plugins/ca-pi/hooks/doctor.py +420 -0
  72. package/plugins/ca-pi/hooks/git-enforce.py +325 -0
  73. package/plugins/ca-pi/hooks/hostapi.py +460 -0
  74. package/plugins/ca-pi/hooks/init-codearbiter.py +225 -0
  75. package/plugins/ca-pi/hooks/metrics.py +62 -0
  76. package/plugins/ca-pi/hooks/migration-pass.py +129 -0
  77. package/plugins/ca-pi/hooks/pi-bridge.py +543 -0
  78. package/plugins/ca-pi/hooks/post-write-edit.py +231 -0
  79. package/plugins/ca-pi/hooks/pre-bash.py +90 -0
  80. package/plugins/ca-pi/hooks/pre-edit.py +284 -0
  81. package/plugins/ca-pi/hooks/pre-read.py +81 -0
  82. package/plugins/ca-pi/hooks/pre-write.py +217 -0
  83. package/plugins/ca-pi/hooks/preview.py +69 -0
  84. package/plugins/ca-pi/hooks/prune-transcript.py +232 -0
  85. package/plugins/ca-pi/hooks/releasehash.py +216 -0
  86. package/plugins/ca-pi/hooks/security-pass.py +139 -0
  87. package/plugins/ca-pi/hooks/session-start.py +1218 -0
  88. package/plugins/ca-pi/hooks/statusline.py +736 -0
  89. package/plugins/ca-pi/hooks/taskwrite.py +351 -0
  90. package/plugins/ca-pi/hooks/update-refresh.py +51 -0
  91. package/plugins/ca-pi/hooks/wire-statusline.py +435 -0
  92. package/plugins/ca-pi/includes/anti-slop-design/INDEX.md +55 -0
  93. package/plugins/ca-pi/includes/anti-slop-design/color.md +43 -0
  94. package/plugins/ca-pi/includes/anti-slop-design/core.md +244 -0
  95. package/plugins/ca-pi/includes/anti-slop-design/images.md +32 -0
  96. package/plugins/ca-pi/includes/anti-slop-design/layout.md +45 -0
  97. package/plugins/ca-pi/includes/anti-slop-design/medium-cli.md +39 -0
  98. package/plugins/ca-pi/includes/anti-slop-design/medium-dataviz.md +43 -0
  99. package/plugins/ca-pi/includes/anti-slop-design/medium-diagram.md +35 -0
  100. package/plugins/ca-pi/includes/anti-slop-design/medium-documents.md +70 -0
  101. package/plugins/ca-pi/includes/anti-slop-design/medium-slides.md +30 -0
  102. package/plugins/ca-pi/includes/anti-slop-design/medium-web.md +39 -0
  103. package/plugins/ca-pi/includes/anti-slop-design/typography.md +51 -0
  104. package/plugins/ca-pi/includes/author-tdd-workflow.md +14 -0
  105. package/plugins/ca-pi/includes/compaction-charter.md +16 -0
  106. package/plugins/ca-pi/includes/cut-docs.md +16 -0
  107. package/plugins/ca-pi/includes/dev-mode.md +30 -0
  108. package/plugins/ca-pi/includes/farm.md +237 -0
  109. package/plugins/ca-pi/includes/fresh-verification.md +14 -0
  110. package/plugins/ca-pi/includes/harvest.md +69 -0
  111. package/plugins/ca-pi/includes/maturity-coverage.md +102 -0
  112. package/plugins/ca-pi/includes/pi-host-notes.md +69 -0
  113. package/plugins/ca-pi/includes/redirect.md +69 -0
  114. package/plugins/ca-pi/includes/reference-map.md +22 -0
  115. package/plugins/ca-pi/includes/review-matrix.md +14 -0
  116. package/plugins/ca-pi/includes/reviewer-contract.md +53 -0
  117. package/plugins/ca-pi/includes/routing-table.md +47 -0
  118. package/plugins/ca-pi/includes/security-gate-record.md +22 -0
  119. package/plugins/ca-pi/includes/smarts/core.md +90 -0
  120. package/plugins/ca-pi/includes/smarts/decision-log-format.md +56 -0
  121. package/plugins/ca-pi/routines/INDEX.md +32 -0
  122. package/plugins/ca-pi/routines/brainstorming/SKILL.md +122 -0
  123. package/plugins/ca-pi/routines/commit-gate/SKILL.md +151 -0
  124. package/plugins/ca-pi/routines/context-check/SKILL.md +85 -0
  125. package/plugins/ca-pi/routines/context-creation/SKILL.md +171 -0
  126. package/plugins/ca-pi/routines/crypto-compliance/SKILL.md +41 -0
  127. package/plugins/ca-pi/routines/debug/SKILL.md +99 -0
  128. package/plugins/ca-pi/routines/decision-lifecycle/SKILL.md +104 -0
  129. package/plugins/ca-pi/routines/decision-lifecycle/references/adr-template.md +74 -0
  130. package/plugins/ca-pi/routines/decision-variance/SKILL.md +147 -0
  131. package/plugins/ca-pi/routines/decompose/SKILL.md +168 -0
  132. package/plugins/ca-pi/routines/dispatching-parallel-agents/SKILL.md +76 -0
  133. package/plugins/ca-pi/routines/executing-plans/SKILL.md +83 -0
  134. package/plugins/ca-pi/routines/finishing-a-development-branch/SKILL.md +91 -0
  135. package/plugins/ca-pi/routines/post-merge-cleanup/SKILL.md +233 -0
  136. package/plugins/ca-pi/routines/refactor/SKILL.md +91 -0
  137. package/plugins/ca-pi/routines/release/SKILL.md +315 -0
  138. package/plugins/ca-pi/routines/secret-handling/SKILL.md +67 -0
  139. package/plugins/ca-pi/routines/security-architecture/SKILL.md +63 -0
  140. package/plugins/ca-pi/routines/skill-author/SKILL.md +108 -0
  141. package/plugins/ca-pi/routines/skill-author/references/skill-template.md +58 -0
  142. package/plugins/ca-pi/routines/subagent-driven-development/SKILL.md +149 -0
  143. package/plugins/ca-pi/routines/subagent-driven-development/references/farm-dispatch.md +145 -0
  144. package/plugins/ca-pi/routines/tdd/SKILL.md +139 -0
  145. package/plugins/ca-pi/routines/tribunal/SKILL.md +109 -0
  146. package/plugins/ca-pi/routines/tribunal/references/ai-markers.md +29 -0
  147. package/plugins/ca-pi/routines/tribunal/references/cost-and-models.md +64 -0
  148. package/plugins/ca-pi/routines/tribunal/references/finding-record.md +27 -0
  149. package/plugins/ca-pi/routines/tribunal/references/issue-filing.md +47 -0
  150. package/plugins/ca-pi/routines/tribunal/references/lenses/appsec.md +22 -0
  151. package/plugins/ca-pi/routines/tribunal/references/lenses/architecture.md +23 -0
  152. package/plugins/ca-pi/routines/tribunal/references/lenses/coverage.md +20 -0
  153. package/plugins/ca-pi/routines/tribunal/references/lenses/infra.md +24 -0
  154. package/plugins/ca-pi/routines/tribunal/references/lenses/migration.md +22 -0
  155. package/plugins/ca-pi/routines/tribunal/references/lenses/observability.md +21 -0
  156. package/plugins/ca-pi/routines/tribunal/references/lenses/performance.md +22 -0
  157. package/plugins/ca-pi/routines/tribunal/references/lenses/reliability.md +23 -0
  158. package/plugins/ca-pi/routines/tribunal/references/lenses/secrets-supply.md +22 -0
  159. package/plugins/ca-pi/routines/tribunal/references/lenses/test-fidelity.md +24 -0
  160. package/plugins/ca-pi/routines/tribunal/references/lenses/typesafety.md +21 -0
  161. package/plugins/ca-pi/routines/tribunal/references/report.md +19 -0
  162. package/plugins/ca-pi/routines/tribunal/references/schemas.md +58 -0
  163. package/plugins/ca-pi/routines/tribunal/references/telemetry.md +28 -0
  164. package/plugins/ca-pi/routines/tribunal/references/triage.md +53 -0
  165. package/plugins/ca-pi/routines/using-git-worktrees/SKILL.md +85 -0
  166. package/plugins/ca-pi/routines/writing-plans/SKILL.md +129 -0
  167. package/plugins/ca-pi/routines/writing-plans/references/farm-plan.md +50 -0
  168. package/plugins/ca-pi/skills/ca-add-dep/SKILL.md +88 -0
  169. package/plugins/ca-pi/skills/ca-adr/SKILL.md +30 -0
  170. package/plugins/ca-pi/skills/ca-adr-status/SKILL.md +30 -0
  171. package/plugins/ca-pi/skills/ca-arbiter/SKILL.md +36 -0
  172. package/plugins/ca-pi/skills/ca-audit/SKILL.md +51 -0
  173. package/plugins/ca-pi/skills/ca-btw/SKILL.md +23 -0
  174. package/plugins/ca-pi/skills/ca-checkpoint/SKILL.md +50 -0
  175. package/plugins/ca-pi/skills/ca-chore/SKILL.md +58 -0
  176. package/plugins/ca-pi/skills/ca-cleanup/SKILL.md +55 -0
  177. package/plugins/ca-pi/skills/ca-commands/SKILL.md +21 -0
  178. package/plugins/ca-pi/skills/ca-commit/SKILL.md +27 -0
  179. package/plugins/ca-pi/skills/ca-conflict/SKILL.md +61 -0
  180. package/plugins/ca-pi/skills/ca-context-check/SKILL.md +32 -0
  181. package/plugins/ca-pi/skills/ca-create-context/SKILL.md +32 -0
  182. package/plugins/ca-pi/skills/ca-debug/SKILL.md +42 -0
  183. package/plugins/ca-pi/skills/ca-decompose/SKILL.md +30 -0
  184. package/plugins/ca-pi/skills/ca-dev/SKILL.md +42 -0
  185. package/plugins/ca-pi/skills/ca-doctor/SKILL.md +44 -0
  186. package/plugins/ca-pi/skills/ca-feature/SKILL.md +105 -0
  187. package/plugins/ca-pi/skills/ca-fix/SKILL.md +42 -0
  188. package/plugins/ca-pi/skills/ca-init/SKILL.md +56 -0
  189. package/plugins/ca-pi/skills/ca-metrics/SKILL.md +80 -0
  190. package/plugins/ca-pi/skills/ca-new-skill/SKILL.md +34 -0
  191. package/plugins/ca-pi/skills/ca-override/SKILL.md +72 -0
  192. package/plugins/ca-pi/skills/ca-pr/SKILL.md +61 -0
  193. package/plugins/ca-pi/skills/ca-preview/SKILL.md +86 -0
  194. package/plugins/ca-pi/skills/ca-prune/SKILL.md +100 -0
  195. package/plugins/ca-pi/skills/ca-reconcile/SKILL.md +43 -0
  196. package/plugins/ca-pi/skills/ca-refactor/SKILL.md +43 -0
  197. package/plugins/ca-pi/skills/ca-release/SKILL.md +57 -0
  198. package/plugins/ca-pi/skills/ca-review/SKILL.md +63 -0
  199. package/plugins/ca-pi/skills/ca-spike/SKILL.md +41 -0
  200. package/plugins/ca-pi/skills/ca-sprint/SKILL.md +44 -0
  201. package/plugins/ca-pi/skills/ca-standup/SKILL.md +112 -0
  202. package/plugins/ca-pi/skills/ca-status/SKILL.md +56 -0
  203. package/plugins/ca-pi/skills/ca-task/SKILL.md +61 -0
  204. package/plugins/ca-pi/skills/ca-threat-model/SKILL.md +45 -0
  205. package/plugins/ca-pi/skills/ca-tribunal/SKILL.md +44 -0
  206. package/plugins/ca-pi/skills/ca-watch/SKILL.md +78 -0
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env python3
2
+ # codeArbiter — host-neutral semantic selection policy for transcript pruning.
3
+ """Host-neutral semantic selection policy for transcript pruning.
4
+
5
+ Codecs own host serialization. This module owns only deterministic selection,
6
+ tier ordering, markers, dry metrics, and audit outcomes.
7
+ """
8
+ #
9
+ # Public API:
10
+ # reduction_metrics(bytes_before, bytes_after, strategy_savings) -> dict
11
+ # file and model-context reductions
12
+ # with shared token estimates
13
+ # audit_outcomes(parse_errors, orphans, unpaired) -> tuple host-neutral integrity
14
+ # levels; codecs retain native messages
15
+ # marker_for(original_text) -> str deterministic condensed-content marker;
16
+ # an existing marker is returned unchanged
17
+ # has_marker(value) -> bool True iff value already carries the marker
18
+ # select_strategies(tier, strategies=None) -> tuple strategies admitted by tier,
19
+ # or an explicit requested subset
20
+ # protected_ordinal(entries, keep_recent) -> int the protected-tail boundary
21
+ # plan_prune(entries, policy) -> PrunePlan full selection/action/audit plan over
22
+ # a SemanticEntry sequence
23
+
24
+ from dataclasses import dataclass
25
+ import hashlib
26
+ import json
27
+ from typing import Optional
28
+
29
+
30
+ MARKER_PREFIX = "[ca-condensed "
31
+ TIERS = {"gentle": 0, "standard": 1, "aggressive": 2}
32
+ STRATEGY_TIERS = {
33
+ "sidecar-collapse": "gentle",
34
+ "reasoning-fold": "standard",
35
+ "mcp-payload-condense": "standard",
36
+ "shell-tail-keep": "standard",
37
+ "superseded-read-condense": "aggressive",
38
+ "repeat-reminder-fold": "aggressive",
39
+ "inline-image-evict": "aggressive",
40
+ "aged-result-condense": "standard",
41
+ "oversize-result-clamp": "gentle",
42
+ }
43
+ STRATEGY_ORDER = (
44
+ "sidecar-collapse",
45
+ "reasoning-fold",
46
+ "mcp-payload-condense",
47
+ "shell-tail-keep",
48
+ "superseded-read-condense",
49
+ "repeat-reminder-fold",
50
+ "inline-image-evict",
51
+ "aged-result-condense",
52
+ "oversize-result-clamp",
53
+ )
54
+ STRATEGY_METRIC_SCOPES = {
55
+ "sidecar-collapse": "file-only",
56
+ "reasoning-fold": "context",
57
+ "mcp-payload-condense": "context",
58
+ "shell-tail-keep": "context",
59
+ "superseded-read-condense": "context",
60
+ "repeat-reminder-fold": "context",
61
+ "inline-image-evict": "context",
62
+ "aged-result-condense": "context",
63
+ "oversize-result-clamp": "context",
64
+ }
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class SemanticEntry:
69
+ id: str
70
+ ordinal: int
71
+ role: str
72
+ kind: str
73
+ byte_size: int
74
+ tool_bearing: bool = False
75
+ marked: bool = False
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class PrunePolicy:
80
+ tier: str = "gentle"
81
+ keep_recent: int = 10
82
+ max_bytes: int = 8192
83
+ strategies: tuple = ()
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class PrunePlan:
88
+ protected_ids: tuple
89
+ first_kept_id: Optional[str]
90
+ protected_from: int
91
+ actions: tuple
92
+ metrics: dict
93
+ audit_codes: tuple
94
+ fingerprint: str
95
+
96
+
97
+ STANDARD_POLICY = PrunePolicy(tier="standard")
98
+
99
+
100
+ def reduction_metrics(bytes_before, bytes_after, strategy_savings):
101
+ """Host-neutral file and model-context reduction metrics."""
102
+ if type(bytes_before) is not int or type(bytes_after) is not int \
103
+ or bytes_before < 0 or bytes_after < 0:
104
+ raise ValueError("prune byte metrics are invalid")
105
+ if not isinstance(strategy_savings, dict):
106
+ raise ValueError("prune strategy savings are invalid")
107
+ context_bytes_freed = 0
108
+ for name, value in strategy_savings.items():
109
+ if name not in STRATEGY_METRIC_SCOPES or type(value) is not int or value < 0:
110
+ raise ValueError("prune strategy savings are invalid")
111
+ if STRATEGY_METRIC_SCOPES[name] == "context":
112
+ context_bytes_freed += value
113
+ file_bytes_freed = bytes_before - bytes_after
114
+ file_pct = (round(100.0 * file_bytes_freed / bytes_before, 1)
115
+ if bytes_before else 0.0)
116
+ return {
117
+ "bytes_before": bytes_before,
118
+ "bytes_after": bytes_after,
119
+ "freed_bytes": file_bytes_freed,
120
+ "est_tokens_before": bytes_before // 4,
121
+ "est_tokens_after": bytes_after // 4,
122
+ "pct": file_pct,
123
+ "file_bytes_freed": file_bytes_freed,
124
+ "file_est_tokens_before": bytes_before // 4,
125
+ "file_est_tokens_after": bytes_after // 4,
126
+ "file_est_tokens_freed": file_bytes_freed // 4,
127
+ "file_pct": file_pct,
128
+ "context_bytes_freed": context_bytes_freed,
129
+ "context_est_tokens_freed": context_bytes_freed // 4,
130
+ }
131
+
132
+
133
+ def audit_outcomes(parse_errors=0, orphans=0, unpaired=0):
134
+ """Select host-neutral integrity levels; codecs retain native messages."""
135
+ return (
136
+ "FAIL" if parse_errors else "OK",
137
+ "WARN" if orphans else "OK",
138
+ "WARN" if unpaired else "OK",
139
+ "OK",
140
+ )
141
+
142
+
143
+ def marker_for(original_text):
144
+ """Return one deterministic marker; an existing marker is unchanged."""
145
+ if has_marker(original_text):
146
+ return original_text
147
+ data = str(original_text).encode("utf-8")
148
+ digest = hashlib.sha256(data).hexdigest()[:8]
149
+ return f"{MARKER_PREFIX}{len(data)}B #{digest}]"
150
+
151
+
152
+ def has_marker(value):
153
+ return isinstance(value, str) and MARKER_PREFIX in value
154
+
155
+
156
+ def select_strategies(tier, strategies=None):
157
+ if strategies:
158
+ requested = set(strategies)
159
+ return tuple(name for name in STRATEGY_ORDER if name in requested)
160
+ ceiling = TIERS.get(tier, 0)
161
+ return tuple(name for name in STRATEGY_ORDER
162
+ if TIERS[STRATEGY_TIERS[name]] <= ceiling)
163
+
164
+
165
+ def protected_ordinal(entries, keep_recent):
166
+ """Match the existing protected-tail rule over semantic entries."""
167
+ anchors = [entry.ordinal for entry in entries if entry.tool_bearing]
168
+ result_anchors = [entry.ordinal for entry in entries
169
+ if entry.kind == "tool-result"]
170
+ anchors = anchors or result_anchors
171
+ if keep_recent <= 0:
172
+ tool_boundary = len(entries)
173
+ elif len(anchors) > keep_recent:
174
+ tool_boundary = anchors[-keep_recent]
175
+ elif anchors:
176
+ tool_boundary = anchors[0]
177
+ else:
178
+ tool_boundary = len(entries)
179
+ assistants = [entry.ordinal for entry in entries if entry.role == "assistant"]
180
+ return min(tool_boundary, assistants[-1]) if assistants else tool_boundary
181
+
182
+
183
+ def _action_for(entry, selected, policy):
184
+ if entry.marked:
185
+ return "already-condensed"
186
+ if entry.kind == "tool-result" and entry.byte_size > policy.max_bytes \
187
+ and "oversize-result-clamp" in selected:
188
+ return "oversize-result-clamp"
189
+ if entry.role == "assistant" and "reasoning-fold" in selected:
190
+ return "reasoning-fold"
191
+ return "retain"
192
+
193
+
194
+ def plan_prune(entries, policy):
195
+ entries = tuple(entries)
196
+ if any(not isinstance(entry, SemanticEntry) for entry in entries):
197
+ raise TypeError("plan_prune requires SemanticEntry values")
198
+ if [entry.ordinal for entry in entries] != list(range(len(entries))):
199
+ raise ValueError("semantic entry ordinals must be contiguous and ordered")
200
+ boundary = protected_ordinal(entries, policy.keep_recent)
201
+ protected = tuple(entry.id for entry in entries if entry.ordinal >= boundary)
202
+ selected = select_strategies(policy.tier, policy.strategies)
203
+ actions = tuple((entry.id, _action_for(entry, selected, policy))
204
+ for entry in entries if entry.ordinal < boundary)
205
+ marked = sum(1 for entry in entries if entry.ordinal < boundary and entry.marked)
206
+ metrics = {
207
+ "entries_before": len(entries),
208
+ "candidate_entries": boundary,
209
+ "protected_entries": len(protected),
210
+ "marked_candidates": marked,
211
+ }
212
+ audit_codes = (("CA-PRUNE-NOOP",) if boundary == 0
213
+ else ("CA-PRUNE-IDEMPOTENT",) if marked == boundary
214
+ else ("CA-PRUNE-PLAN",))
215
+ fingerprint_source = {
216
+ "boundary": boundary,
217
+ "protected": protected,
218
+ "actions": actions,
219
+ "metrics": metrics,
220
+ "audit": audit_codes,
221
+ "tier": policy.tier,
222
+ "strategies": selected,
223
+ }
224
+ fingerprint = hashlib.sha256(json.dumps(
225
+ fingerprint_source, sort_keys=True, separators=(",", ":")
226
+ ).encode("utf-8")).hexdigest()
227
+ return PrunePlan(
228
+ protected_ids=protected,
229
+ first_kept_id=(entries[boundary].id if boundary < len(entries) else None),
230
+ protected_from=boundary,
231
+ actions=actions,
232
+ metrics=metrics,
233
+ audit_codes=audit_codes,
234
+ fingerprint=fingerprint,
235
+ )