supipowers 1.5.3 → 2.0.1

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 (340) hide show
  1. package/README.md +14 -8
  2. package/bin/install.mjs +20 -5
  3. package/bin/install.ts +95 -0
  4. package/package.json +8 -4
  5. package/skills/context-mode/SKILL.md +17 -10
  6. package/skills/harness/SKILL.md +94 -0
  7. package/skills/ui-design/SKILL.md +63 -0
  8. package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
  9. package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
  10. package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
  11. package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
  12. package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
  13. package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
  14. package/skills/ultraplan-discover/SKILL.md +96 -0
  15. package/skills/ultraplan-intake/SKILL.md +89 -0
  16. package/skills/ultraplan-research/SKILL.md +129 -0
  17. package/skills/ultraplan-review/SKILL.md +86 -0
  18. package/skills/ultraplan-review-scope/SKILL.md +111 -0
  19. package/skills/ultraplan-review-structure/SKILL.md +120 -0
  20. package/skills/ultraplan-review-tdd/SKILL.md +142 -0
  21. package/skills/ultraplan-scout/SKILL.md +110 -0
  22. package/skills/ultraplan-synthesize/SKILL.md +124 -0
  23. package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
  24. package/src/ai/schema-text.ts +129 -0
  25. package/src/ai/structured-output.ts +274 -0
  26. package/src/ai/template.ts +27 -0
  27. package/src/bootstrap.ts +63 -28
  28. package/src/commands/agents.ts +131 -42
  29. package/src/commands/ai-review.ts +251 -30
  30. package/src/commands/clear.ts +434 -0
  31. package/src/commands/commit.ts +1 -0
  32. package/src/commands/config.ts +242 -44
  33. package/src/commands/context.ts +55 -28
  34. package/src/commands/doctor.ts +234 -6
  35. package/src/commands/fix-pr.ts +306 -131
  36. package/src/commands/generate.ts +111 -21
  37. package/src/commands/memory.ts +192 -0
  38. package/src/commands/model-picker.ts +28 -21
  39. package/src/commands/model.ts +18 -8
  40. package/src/commands/optimize-context.ts +408 -29
  41. package/src/commands/plan.ts +2 -0
  42. package/src/commands/qa.ts +312 -137
  43. package/src/commands/release.ts +259 -76
  44. package/src/commands/review.ts +293 -59
  45. package/src/commands/status.ts +200 -13
  46. package/src/commands/supi.ts +3 -35
  47. package/src/commands/ui-design.ts +394 -0
  48. package/src/commands/ultraplan.ts +1518 -0
  49. package/src/commands/update.ts +86 -0
  50. package/src/config/defaults.ts +62 -0
  51. package/src/config/loader.ts +448 -60
  52. package/src/config/schema.ts +108 -2
  53. package/src/context/optimizer.ts +25 -33
  54. package/src/context/rule-renderer.ts +223 -0
  55. package/src/context/savings.ts +258 -0
  56. package/src/context/startup-check.ts +380 -0
  57. package/src/context/startup-optimizer.ts +355 -0
  58. package/src/context/tokenignore.ts +146 -0
  59. package/src/context-mode/cache-handle.ts +49 -0
  60. package/src/context-mode/cache-preview.ts +71 -0
  61. package/src/context-mode/cache-store.ts +738 -0
  62. package/src/context-mode/compressor.ts +131 -26
  63. package/src/context-mode/dedup.ts +108 -0
  64. package/src/context-mode/detector.ts +35 -4
  65. package/src/context-mode/event-extractor.ts +14 -12
  66. package/src/context-mode/event-store.ts +91 -36
  67. package/src/context-mode/hooks.ts +798 -56
  68. package/src/context-mode/knowledge/store.ts +255 -11
  69. package/src/context-mode/memory-store.ts +325 -0
  70. package/src/context-mode/metrics-recorder.ts +158 -0
  71. package/src/context-mode/metrics-store.ts +765 -0
  72. package/src/context-mode/model.ts +24 -0
  73. package/src/context-mode/processor-keys.ts +29 -0
  74. package/src/context-mode/processors/build.ts +66 -0
  75. package/src/context-mode/processors/docker.ts +57 -0
  76. package/src/context-mode/processors/git.ts +111 -0
  77. package/src/context-mode/processors/json.ts +112 -0
  78. package/src/context-mode/processors/k8s.ts +67 -0
  79. package/src/context-mode/processors/lint.ts +67 -0
  80. package/src/context-mode/processors/log.ts +86 -0
  81. package/src/context-mode/processors/registry.ts +116 -0
  82. package/src/context-mode/processors/test-runner.ts +102 -0
  83. package/src/context-mode/processors/types.ts +20 -0
  84. package/src/context-mode/repomap.ts +400 -0
  85. package/src/context-mode/routing.ts +97 -24
  86. package/src/context-mode/sandbox/runners.ts +5 -1
  87. package/src/context-mode/snapshot-builder.ts +106 -11
  88. package/src/context-mode/source-hash.ts +173 -0
  89. package/src/context-mode/tool-name.ts +11 -0
  90. package/src/context-mode/tools.ts +654 -22
  91. package/src/context-mode/web/fetcher.ts +31 -12
  92. package/src/debug/logger.ts +2 -1
  93. package/src/deps/registry.ts +1 -1
  94. package/src/discipline/failure-summarizer.ts +170 -0
  95. package/src/discipline/failure-taxonomy.ts +131 -0
  96. package/src/discipline/workflow-invariants.ts +125 -0
  97. package/src/discovery/index.ts +31 -0
  98. package/src/discovery/lsp.ts +87 -0
  99. package/src/discovery/rank.ts +144 -0
  100. package/src/discovery/sources.ts +89 -0
  101. package/src/discovery/workflow.ts +87 -0
  102. package/src/docs/contracts.ts +39 -0
  103. package/src/docs/drift.ts +117 -87
  104. package/src/fix-pr/assessment.ts +200 -0
  105. package/src/fix-pr/contracts.ts +47 -0
  106. package/src/fix-pr/fetch-comments.ts +80 -0
  107. package/src/fix-pr/prompt-builder.ts +58 -40
  108. package/src/fix-pr/scripts/exec.ts +34 -0
  109. package/src/fix-pr/scripts/trigger-review.ts +106 -0
  110. package/src/fix-pr/scripts/wait-and-check.ts +108 -0
  111. package/src/fix-pr/types.ts +4 -0
  112. package/src/git/branch-finish.ts +5 -0
  113. package/src/git/commit-contract.ts +83 -0
  114. package/src/git/commit.ts +121 -184
  115. package/src/git/status.ts +62 -8
  116. package/src/harness/anti_slop/architecture-parser.ts +210 -0
  117. package/src/harness/anti_slop/backend-factory.ts +30 -0
  118. package/src/harness/anti_slop/backend.ts +140 -0
  119. package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
  120. package/src/harness/anti_slop/fallow-adapter.ts +305 -0
  121. package/src/harness/anti_slop/installer.ts +227 -0
  122. package/src/harness/anti_slop/queue.ts +216 -0
  123. package/src/harness/anti_slop/recommend.ts +84 -0
  124. package/src/harness/anti_slop/score.ts +180 -0
  125. package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
  126. package/src/harness/artifacts/agents-md.ts +88 -0
  127. package/src/harness/artifacts/checks-wiring.ts +57 -0
  128. package/src/harness/artifacts/docs-tree.ts +79 -0
  129. package/src/harness/artifacts/lint-configs.ts +136 -0
  130. package/src/harness/artifacts/review-agents.ts +67 -0
  131. package/src/harness/bare-entry.ts +108 -0
  132. package/src/harness/command.ts +1010 -0
  133. package/src/harness/default-agents/design.md +23 -0
  134. package/src/harness/default-agents/discover.md +18 -0
  135. package/src/harness/default-agents/implement.md +24 -0
  136. package/src/harness/default-agents/plan.md +19 -0
  137. package/src/harness/default-agents/research.md +21 -0
  138. package/src/harness/default-agents/validate.md +22 -0
  139. package/src/harness/gc/reporter.ts +28 -0
  140. package/src/harness/gc/runner.ts +136 -0
  141. package/src/harness/hooks/layer-context-inject.ts +155 -0
  142. package/src/harness/hooks/post-session-sweep.ts +130 -0
  143. package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
  144. package/src/harness/hooks/register.ts +118 -0
  145. package/src/harness/model.ts +117 -0
  146. package/src/harness/pipeline.ts +348 -0
  147. package/src/harness/project-paths.ts +235 -0
  148. package/src/harness/stage-runner.ts +107 -0
  149. package/src/harness/stages/design.ts +386 -0
  150. package/src/harness/stages/discover.ts +454 -0
  151. package/src/harness/stages/implement.ts +162 -0
  152. package/src/harness/stages/plan.ts +335 -0
  153. package/src/harness/stages/research.ts +263 -0
  154. package/src/harness/stages/validate.ts +684 -0
  155. package/src/harness/storage.ts +467 -0
  156. package/src/harness/tools.ts +426 -0
  157. package/src/lsp/bridge.ts +56 -95
  158. package/src/lsp/capabilities.ts +108 -0
  159. package/src/lsp/contracts.ts +35 -0
  160. package/src/lsp/detector.ts +8 -12
  161. package/src/markdown-frontmatter.ts +68 -0
  162. package/src/mempalace/bridge.ts +135 -0
  163. package/src/mempalace/config.ts +75 -0
  164. package/src/mempalace/format.ts +163 -0
  165. package/src/mempalace/hooks.ts +370 -0
  166. package/src/mempalace/installer-helper.ts +194 -0
  167. package/src/mempalace/python/mempalace_bridge.py +440 -0
  168. package/src/mempalace/runtime.ts +565 -0
  169. package/src/mempalace/schema.ts +268 -0
  170. package/src/mempalace/session-summary.ts +198 -0
  171. package/src/mempalace/tool.ts +186 -0
  172. package/src/mempalace/uv.ts +256 -0
  173. package/src/migrate/runner.ts +354 -0
  174. package/src/planning/approval-flow.ts +206 -9
  175. package/src/planning/plan-writer-prompt.ts +4 -3
  176. package/src/planning/planning-ask-tool.ts +39 -0
  177. package/src/planning/render-markdown.ts +74 -0
  178. package/src/planning/spec.ts +42 -0
  179. package/src/planning/system-prompt.ts +11 -8
  180. package/src/planning/validate.ts +84 -0
  181. package/src/platform/omp.ts +15 -2
  182. package/src/platform/system-prompt.ts +37 -0
  183. package/src/platform/test-utils.ts +3 -0
  184. package/src/platform/types.ts +6 -1
  185. package/src/qa/config.ts +12 -6
  186. package/src/qa/detect-app-type.ts +13 -6
  187. package/src/qa/matrix.ts +12 -6
  188. package/src/qa/prompt-builder.ts +28 -30
  189. package/src/qa/scripts/dev-server-utils.ts +72 -0
  190. package/src/qa/scripts/run-e2e-tests.ts +226 -0
  191. package/src/qa/scripts/start-dev-server.ts +138 -0
  192. package/src/qa/scripts/stop-dev-server.ts +77 -0
  193. package/src/qa/session.ts +13 -7
  194. package/src/quality/ai-setup.ts +27 -25
  195. package/src/quality/contracts.ts +34 -0
  196. package/src/quality/gates/ai-review.ts +20 -58
  197. package/src/quality/gates/command.ts +249 -46
  198. package/src/quality/review-gates.ts +18 -2
  199. package/src/quality/runner.ts +63 -22
  200. package/src/quality/schemas.ts +37 -2
  201. package/src/quality/setup.ts +96 -16
  202. package/src/release/changelog.ts +1 -1
  203. package/src/release/channels/custom.ts +13 -3
  204. package/src/release/channels/types.ts +5 -0
  205. package/src/release/contracts.ts +90 -0
  206. package/src/release/executor.ts +122 -45
  207. package/src/release/prompt.ts +18 -2
  208. package/src/release/targets.ts +86 -0
  209. package/src/release/version.ts +96 -71
  210. package/src/review/agent-loader.ts +221 -109
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +114 -13
  213. package/src/review/output.ts +12 -139
  214. package/src/review/runner.ts +12 -6
  215. package/src/review/scope.ts +144 -24
  216. package/src/review/types.ts +1 -20
  217. package/src/review/validator.ts +12 -6
  218. package/src/storage/fix-pr-sessions.ts +21 -14
  219. package/src/storage/plans.ts +14 -5
  220. package/src/storage/qa-sessions.ts +25 -19
  221. package/src/storage/reliability-metrics.ts +180 -0
  222. package/src/storage/reports.ts +8 -7
  223. package/src/storage/review-sessions.ts +55 -20
  224. package/src/tool-catalog/active-tool-controller.ts +164 -0
  225. package/src/tool-catalog/active-tool-planner.ts +212 -0
  226. package/src/tool-catalog/tool-groups.ts +102 -0
  227. package/src/types.ts +1399 -5
  228. package/src/ui-design/backend-adapter.ts +78 -0
  229. package/src/ui-design/backends/local-html.ts +82 -0
  230. package/src/ui-design/backends/pencil-mcp.ts +111 -0
  231. package/src/ui-design/components-scanner.ts +124 -0
  232. package/src/ui-design/config.ts +55 -0
  233. package/src/ui-design/pen-scanner.ts +95 -0
  234. package/src/ui-design/pen-selector.ts +72 -0
  235. package/src/ui-design/prompt-builder.ts +73 -0
  236. package/src/ui-design/scanner.ts +136 -0
  237. package/src/ui-design/session.ts +974 -0
  238. package/src/ui-design/system-prompt.ts +312 -0
  239. package/src/ui-design/tokens-scanner.ts +181 -0
  240. package/src/ui-design/types.ts +96 -0
  241. package/src/ultraplan/agent-catalog.ts +522 -0
  242. package/src/ultraplan/authoring/agent-catalog.ts +310 -0
  243. package/src/ultraplan/authoring/authoring-tools.ts +552 -0
  244. package/src/ultraplan/authoring/command-handlers.ts +339 -0
  245. package/src/ultraplan/authoring/markdown.ts +510 -0
  246. package/src/ultraplan/authoring/model.ts +162 -0
  247. package/src/ultraplan/authoring/pipeline.ts +319 -0
  248. package/src/ultraplan/authoring/stage-runner.ts +141 -0
  249. package/src/ultraplan/authoring/stages/approve.ts +249 -0
  250. package/src/ultraplan/authoring/stages/discover.ts +289 -0
  251. package/src/ultraplan/authoring/stages/intake.ts +203 -0
  252. package/src/ultraplan/authoring/stages/research.ts +399 -0
  253. package/src/ultraplan/authoring/stages/review.ts +333 -0
  254. package/src/ultraplan/authoring/stages/scout.ts +188 -0
  255. package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
  256. package/src/ultraplan/authoring/storage.ts +594 -0
  257. package/src/ultraplan/authoring/synth-gate.ts +165 -0
  258. package/src/ultraplan/authoring-draft.ts +653 -0
  259. package/src/ultraplan/authoring-persist.ts +180 -0
  260. package/src/ultraplan/authoring-tool.ts +608 -0
  261. package/src/ultraplan/authoring-wizard.ts +587 -0
  262. package/src/ultraplan/batch/merge.ts +98 -0
  263. package/src/ultraplan/batch/planner.ts +150 -0
  264. package/src/ultraplan/batch/presenter.ts +97 -0
  265. package/src/ultraplan/batch/storage.ts +420 -0
  266. package/src/ultraplan/batch/supervisor.ts +317 -0
  267. package/src/ultraplan/batch/worker.ts +26 -0
  268. package/src/ultraplan/batch/worktree.ts +110 -0
  269. package/src/ultraplan/contracts.ts +1593 -0
  270. package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
  271. package/src/ultraplan/default-agents/authoring/intake.md +12 -0
  272. package/src/ultraplan/default-agents/authoring/planner.md +12 -0
  273. package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
  274. package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
  275. package/src/ultraplan/default-agents/authoring/scout.md +12 -0
  276. package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
  277. package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
  278. package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
  279. package/src/ultraplan/default-agents/backend-executor.md +10 -0
  280. package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
  281. package/src/ultraplan/default-agents/backend-tester.md +10 -0
  282. package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
  283. package/src/ultraplan/default-agents/frontend-executor.md +10 -0
  284. package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
  285. package/src/ultraplan/default-agents/frontend-tester.md +10 -0
  286. package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
  287. package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
  288. package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
  289. package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
  290. package/src/ultraplan/execution/contract.ts +71 -0
  291. package/src/ultraplan/execution/policy.ts +217 -0
  292. package/src/ultraplan/execution/runtime-tools.ts +107 -0
  293. package/src/ultraplan/execution/session-runner.ts +281 -0
  294. package/src/ultraplan/next-router.ts +85 -0
  295. package/src/ultraplan/presenter.ts +359 -0
  296. package/src/ultraplan/project-paths.ts +342 -0
  297. package/src/ultraplan/runtime/active-execution.ts +72 -0
  298. package/src/ultraplan/runtime/apply-mutation.ts +416 -0
  299. package/src/ultraplan/runtime/blockers.ts +243 -0
  300. package/src/ultraplan/runtime/hook-bridge.ts +486 -0
  301. package/src/ultraplan/runtime/launch-context.ts +207 -0
  302. package/src/ultraplan/runtime/migration.ts +524 -0
  303. package/src/ultraplan/runtime/normalize.ts +281 -0
  304. package/src/ultraplan/runtime/proof.ts +260 -0
  305. package/src/ultraplan/runtime/reducer.ts +416 -0
  306. package/src/ultraplan/runtime/repair.ts +251 -0
  307. package/src/ultraplan/runtime/tracker-storage.ts +368 -0
  308. package/src/ultraplan/session-selection.ts +291 -0
  309. package/src/ultraplan/storage.ts +374 -0
  310. package/src/utils/editor.ts +38 -0
  311. package/src/utils/executable.ts +80 -0
  312. package/src/utils/paths.ts +1 -20
  313. package/src/utils/shell.ts +31 -0
  314. package/src/visual/companion.ts +2 -1
  315. package/src/visual/scripts/frame-template.html +60 -0
  316. package/src/visual/scripts/index.js +59 -13
  317. package/src/visual/scripts/package.json +3 -0
  318. package/src/visual/start-server.ts +2 -1
  319. package/src/workspace/git-scope.ts +64 -0
  320. package/src/workspace/locks.ts +23 -0
  321. package/src/workspace/package-manager.ts +117 -0
  322. package/src/workspace/path-mapping.ts +75 -0
  323. package/src/workspace/project-slug.ts +92 -0
  324. package/src/workspace/repo-root.ts +137 -0
  325. package/src/workspace/selector.ts +115 -0
  326. package/src/workspace/state-paths.ts +118 -0
  327. package/src/workspace/targets.ts +313 -0
  328. package/src/fix-pr/scripts/diff-comments.sh +0 -33
  329. package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
  330. package/src/fix-pr/scripts/trigger-review.sh +0 -36
  331. package/src/fix-pr/scripts/wait-and-check.sh +0 -37
  332. package/src/qa/scripts/detect-app-type.sh +0 -68
  333. package/src/qa/scripts/discover-routes.sh +0 -143
  334. package/src/qa/scripts/run-e2e-tests.sh +0 -131
  335. package/src/qa/scripts/start-dev-server.sh +0 -46
  336. package/src/qa/scripts/stop-dev-server.sh +0 -36
  337. package/src/review/prompts/fix-output-schema.md +0 -18
  338. package/src/review/prompts/review-output-schema.md +0 -38
  339. package/src/review/template.ts +0 -15
  340. /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
@@ -0,0 +1,24 @@
1
+ // src/context-mode/model.ts
2
+ //
3
+ // Registers the model-action ID used by the context-mode compaction-time
4
+ // LLM summarizer. The summarizer is wired in `src/context-mode/hooks.ts`
5
+ // at `session_before_compact` and gated by
6
+ // `contextMode.llmSummarization && byteLength(snapshot) > contextMode.llmThreshold`.
7
+ //
8
+ // Registering here (rather than at the call site) keeps registration as a
9
+ // module side-effect, mirroring the pattern in `src/quality/ai-setup.ts`,
10
+ // `src/commands/plan.ts`, etc.
11
+
12
+ import { modelRegistry } from "../config/model-registry-instance.js";
13
+
14
+ export const COMPACTION_SUMMARIZER_ACTION_ID = "context-mode.compaction-summarizer";
15
+
16
+ modelRegistry.register({
17
+ id: COMPACTION_SUMMARIZER_ACTION_ID,
18
+ category: "command",
19
+ label: "Compaction summarizer",
20
+ // Summarization is cheap text reduction; the cheapest available model is
21
+ // fine. "default" keeps it inheriting from the user's main session model
22
+ // when no per-action override is configured.
23
+ harnessRoleHint: "default",
24
+ });
@@ -0,0 +1,29 @@
1
+ // src/context-mode/processor-keys.ts
2
+ //
3
+ // Canonical mapping from a canonical native-tool name to the
4
+ // `ProcessorKey` used for both metric rows and compressor labels.
5
+ //
6
+ // Two consumers of this map exist today:
7
+ // - the compressor labels its emitted result with the same processor key
8
+ // that the metrics row will carry.
9
+ // - the metrics recorder classifies the row when the compressor passed
10
+ // a result through.
11
+ // Importing from one source prevents the two paths from drifting.
12
+ import type { ProcessorKey } from "./metrics-store.js";
13
+
14
+ /**
15
+ * Canonical processor key for each native tool the metrics layer recognises.
16
+ * Tools not listed here resolve to `null` (the recorder treats them as
17
+ * unknown; the compressor short-circuits to no-op).
18
+ */
19
+ export const NATIVE_TOOL_PROCESSOR_KEYS: Record<string, ProcessorKey> = {
20
+ bash: "bash",
21
+ read: "read",
22
+ search: "search",
23
+ find: "find",
24
+ };
25
+
26
+ /** Resolve the canonical processor key for a given canonical tool name. */
27
+ export function processorKeyForTool(canonicalTool: string): ProcessorKey {
28
+ return NATIVE_TOOL_PROCESSOR_KEYS[canonicalTool] ?? null;
29
+ }
@@ -0,0 +1,66 @@
1
+ import type { ProcessorContext, ProcessorInvariant, ProcessorOutput } from "./types.js";
2
+
3
+ const encoder = new TextEncoder();
4
+
5
+ export const BUILD_INVARIANT: ProcessorInvariant = {
6
+ key: "build",
7
+ maxBytes: 8192,
8
+ preserve: ["error diagnostics", "file/line/column", "warnings", "summary tail"],
9
+ };
10
+
11
+ const ERROR_SHAPE_RE = /\b(?:error|ERROR|failed|FAIL)\b|\S+\.ts\(\d+,\d+\)|\S+\.(?:rs|go|ts):\d+:\d+/i;
12
+
13
+ function byteLength(text: string): number {
14
+ return encoder.encode(text).byteLength;
15
+ }
16
+
17
+ function normalizeEol(text: string, eol: ProcessorContext["eol"]): string {
18
+ return text.replace(/\r?\n/g, eol);
19
+ }
20
+
21
+ function isImportant(line: string): boolean {
22
+ return ERROR_SHAPE_RE.test(line)
23
+ || /\bwarning\b/i.test(line)
24
+ || /^\s*-->/i.test(line)
25
+ || /^\s*\d+ \|/.test(line)
26
+ || /^\s*[\^╵]/.test(line)
27
+ || /Found \d+ errors?|\d+ errors?|could not compile|Build failed/i.test(line);
28
+ }
29
+
30
+ function capLines(lines: string[], eol: ProcessorContext["eol"]): string {
31
+ let output = lines.join(eol);
32
+ if (byteLength(output) <= BUILD_INVARIANT.maxBytes) return output;
33
+
34
+ const marker = `[...build processor omitted lines to stay under ${BUILD_INVARIANT.maxBytes} bytes...]`;
35
+ const kept: string[] = [];
36
+ for (const line of lines) {
37
+ const candidate = [...kept, line, marker].join(eol);
38
+ if (byteLength(candidate) > BUILD_INVARIANT.maxBytes) break;
39
+ kept.push(line);
40
+ }
41
+ output = [...kept, marker].join(eol);
42
+ while (byteLength(output) > BUILD_INVARIANT.maxBytes && kept.length > 0) {
43
+ kept.pop();
44
+ output = [...kept, marker].join(eol);
45
+ }
46
+ return output;
47
+ }
48
+
49
+ export function buildProcessor(text: string, ctx: ProcessorContext): ProcessorOutput {
50
+ if (!ERROR_SHAPE_RE.test(text)) {
51
+ return { text, processorKey: "build", passthrough: true };
52
+ }
53
+
54
+ const normalized = normalizeEol(text, ctx.eol);
55
+ const lines = normalized.split(ctx.eol);
56
+ const keep = new Set<number>();
57
+ for (let index = 0; index < lines.length; index += 1) {
58
+ if (!isImportant(lines[index])) continue;
59
+ keep.add(index);
60
+ if (index > 0) keep.add(index - 1);
61
+ if (index + 1 < lines.length) keep.add(index + 1);
62
+ }
63
+
64
+ const selected = [...keep].sort((a, b) => a - b).map((index) => lines[index]);
65
+ return { text: capLines(selected.length > 0 ? selected : lines, ctx.eol), processorKey: "build", passthrough: false };
66
+ }
@@ -0,0 +1,57 @@
1
+ import type { ProcessorContext, ProcessorInvariant, ProcessorOutput } from "./types.js";
2
+
3
+ const encoder = new TextEncoder();
4
+
5
+ export const DOCKER_INVARIANT: ProcessorInvariant = {
6
+ key: "docker",
7
+ maxBytes: 8192,
8
+ preserve: ["table headers", "12-character ids", "names", "statuses", "last 20 log lines"],
9
+ };
10
+
11
+ function byteLength(text: string): number {
12
+ return encoder.encode(text).byteLength;
13
+ }
14
+
15
+ function normalizeEol(text: string, eol: ProcessorContext["eol"]): string {
16
+ return text.replace(/\r?\n/g, eol);
17
+ }
18
+
19
+ function capLines(lines: string[], eol: ProcessorContext["eol"]): string {
20
+ let output = lines.join(eol);
21
+ if (byteLength(output) <= DOCKER_INVARIANT.maxBytes) return output;
22
+ const marker = `[...docker processor omitted lines to stay under ${DOCKER_INVARIANT.maxBytes} bytes...]`;
23
+ const kept: string[] = [];
24
+ for (const line of lines) {
25
+ const candidate = [...kept, line, marker].join(eol);
26
+ if (byteLength(candidate) > DOCKER_INVARIANT.maxBytes) break;
27
+ kept.push(line);
28
+ }
29
+ return [...kept, marker].join(eol);
30
+ }
31
+
32
+ function isDockerTable(lines: string[]): boolean {
33
+ return lines.some((line) => /\b(?:CONTAINER ID|IMAGE ID)\b/.test(line));
34
+ }
35
+
36
+ function isBuildOutput(lines: string[]): boolean {
37
+ return lines.some((line) => /\bERROR\b|failed to solve|exit code:/i.test(line));
38
+ }
39
+
40
+ export function dockerProcessor(text: string, ctx: ProcessorContext): ProcessorOutput {
41
+ const normalized = normalizeEol(text, ctx.eol);
42
+ const lines = normalized.split(ctx.eol).filter((line) => line.length > 0);
43
+
44
+ let compressed: string | null = null;
45
+ if (isDockerTable(lines)) {
46
+ compressed = capLines(lines, ctx.eol);
47
+ } else if (isBuildOutput(lines)) {
48
+ compressed = capLines(lines.filter((line) => /#\d+|ERROR|failed|exit code/i.test(line)), ctx.eol);
49
+ } else if (lines.length > 20) {
50
+ compressed = capLines(lines.slice(-20), ctx.eol);
51
+ }
52
+
53
+ if (compressed === null) {
54
+ return { text, processorKey: "docker", passthrough: true };
55
+ }
56
+ return { text: compressed, processorKey: "docker", passthrough: false };
57
+ }
@@ -0,0 +1,111 @@
1
+ import type { ProcessorContext, ProcessorInvariant, ProcessorOutput } from "./types.js";
2
+
3
+ const encoder = new TextEncoder();
4
+
5
+ export const GIT_INVARIANT: ProcessorInvariant = {
6
+ key: "git",
7
+ maxBytes: 4096,
8
+ preserve: [
9
+ "branch line",
10
+ "per-path status codes",
11
+ "rename from/to lines",
12
+ "hunk markers",
13
+ "net plus/minus counts",
14
+ "last five commits",
15
+ ],
16
+ };
17
+
18
+ function normalizeEol(text: string, eol: ProcessorContext["eol"]): string {
19
+ return text.replace(/\r?\n/g, eol);
20
+ }
21
+
22
+ function byteLength(text: string): number {
23
+ return encoder.encode(text).byteLength;
24
+ }
25
+
26
+ function capText(text: string, maxBytes: number, eol: ProcessorContext["eol"]): string {
27
+ if (byteLength(text) <= maxBytes) return text;
28
+
29
+ const lines = text.split(/\r?\n/);
30
+ const marker = `[...git processor omitted ${lines.length} original lines to stay under ${maxBytes} bytes...]`;
31
+ const kept: string[] = [];
32
+ for (const line of lines) {
33
+ const candidate = [...kept, line, marker].join(eol);
34
+ if (byteLength(candidate) > maxBytes) break;
35
+ kept.push(line);
36
+ }
37
+
38
+ let capped = [...kept, marker].join(eol);
39
+ while (byteLength(capped) > maxBytes && kept.length > 0) {
40
+ kept.pop();
41
+ capped = [...kept, marker].join(eol);
42
+ }
43
+ return capped;
44
+ }
45
+
46
+ function countDiffChanges(text: string): { plus: number; minus: number } {
47
+ let plus = 0;
48
+ let minus = 0;
49
+ for (const line of text.split(/\r?\n/)) {
50
+ if (line.startsWith("+++") || line.startsWith("---")) continue;
51
+ if (line.startsWith("+")) plus += 1;
52
+ if (line.startsWith("-")) minus += 1;
53
+ }
54
+ return { plus, minus };
55
+ }
56
+
57
+ function looksLikeDiff(text: string): boolean {
58
+ return /^diff --git /m.test(text) || /^@@ /m.test(text);
59
+ }
60
+
61
+ function looksLikeLog(text: string): boolean {
62
+ const lines = text.split(/\r?\n/).filter(Boolean);
63
+ if (lines.length === 0) return false;
64
+ return lines.every((line) => /^[0-9a-z]{7,40}\s+/.test(line));
65
+ }
66
+
67
+ function looksLikeStatus(text: string): boolean {
68
+ return /^##\s+/m.test(text) || /^(?:[ MARC?D!U]{1,2}|R\s)\s+\S/m.test(text);
69
+ }
70
+
71
+ function compressDiff(text: string, ctx: ProcessorContext): string {
72
+ const normalized = normalizeEol(text, ctx.eol);
73
+ const { plus, minus } = countDiffChanges(text);
74
+ return capText(`Net changes: +${plus} -${minus}${ctx.eol}${normalized}`, GIT_INVARIANT.maxBytes, ctx.eol);
75
+ }
76
+
77
+ function compressLog(text: string, ctx: ProcessorContext): string {
78
+ const normalized = normalizeEol(text, ctx.eol);
79
+ const lines = normalized.split(ctx.eol).filter(Boolean);
80
+ if (lines.length <= 10) return capText(normalized, GIT_INVARIANT.maxBytes, ctx.eol);
81
+ const head = lines.slice(0, 5);
82
+ const tail = lines.slice(-5);
83
+ return capText(
84
+ [...head, `[...git log omitted ${lines.length - head.length - tail.length} commits...]`, ...tail].join(ctx.eol),
85
+ GIT_INVARIANT.maxBytes,
86
+ ctx.eol,
87
+ );
88
+ }
89
+
90
+ function compressStatusOrOther(text: string, ctx: ProcessorContext): string {
91
+ return capText(normalizeEol(text, ctx.eol), GIT_INVARIANT.maxBytes, ctx.eol);
92
+ }
93
+
94
+ export function gitProcessor(text: string, ctx: ProcessorContext): ProcessorOutput {
95
+ if (ctx.exitCode !== null && ctx.exitCode !== 0) {
96
+ return { text, processorKey: "git", passthrough: true };
97
+ }
98
+
99
+ let compressed: string;
100
+ if (looksLikeDiff(text)) {
101
+ compressed = compressDiff(text, ctx);
102
+ } else if (looksLikeLog(text)) {
103
+ compressed = compressLog(text, ctx);
104
+ } else if (looksLikeStatus(text)) {
105
+ compressed = compressStatusOrOther(text, ctx);
106
+ } else {
107
+ compressed = compressStatusOrOther(text, ctx);
108
+ }
109
+
110
+ return { text: compressed, processorKey: "git", passthrough: false };
111
+ }
@@ -0,0 +1,112 @@
1
+ import type { ProcessorContext, ProcessorInvariant, ProcessorOutput } from "./types.js";
2
+
3
+ const encoder = new TextEncoder();
4
+
5
+ export const JSON_INVARIANT: ProcessorInvariant = {
6
+ key: "json",
7
+ maxBytes: 4096,
8
+ preserve: [
9
+ "top-level keys",
10
+ "array item counts",
11
+ "first five array elements",
12
+ "nested object key counts",
13
+ "parse failures passthrough",
14
+ ],
15
+ };
16
+
17
+ function byteLength(text: string): number {
18
+ return encoder.encode(text).byteLength;
19
+ }
20
+
21
+ function tryParseJson(text: string): unknown | null {
22
+ const trimmed = text.trim();
23
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
24
+ try {
25
+ const parsed = JSON.parse(trimmed) as unknown;
26
+ return parsed !== null && (Array.isArray(parsed) || typeof parsed === "object") ? parsed : null;
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ export function jsonContentSniff(text: string): boolean {
33
+ return tryParseJson(text) !== null;
34
+ }
35
+
36
+ function summarizeValue(value: unknown): string {
37
+ if (Array.isArray(value)) return `array(items=${value.length})`;
38
+ if (value === null) return "null";
39
+ switch (typeof value) {
40
+ case "object":
41
+ return `object(keys=${Object.keys(value as Record<string, unknown>).length})`;
42
+ case "string":
43
+ return `string(len=${value.length})`;
44
+ case "number":
45
+ return "number";
46
+ case "boolean":
47
+ return "boolean";
48
+ default:
49
+ return typeof value;
50
+ }
51
+ }
52
+
53
+ function summarizeArrayItem(value: unknown): string {
54
+ if (Array.isArray(value)) return `[array(${value.length})]`;
55
+ if (value && typeof value === "object") {
56
+ return `{${Object.keys(value as Record<string, unknown>).join(", ")}}`;
57
+ }
58
+ if (typeof value === "string") return JSON.stringify(value);
59
+ return String(value);
60
+ }
61
+
62
+ function capLines(lines: string[], eol: ProcessorContext["eol"]): string {
63
+ let output = lines.join(eol);
64
+ if (byteLength(output) <= JSON_INVARIANT.maxBytes) return output;
65
+
66
+ const marker = `[...json processor omitted lines to stay under ${JSON_INVARIANT.maxBytes} bytes...]`;
67
+ const kept: string[] = [];
68
+ for (const line of lines) {
69
+ const candidate = [...kept, line, marker].join(eol);
70
+ if (byteLength(candidate) > JSON_INVARIANT.maxBytes) break;
71
+ kept.push(line);
72
+ }
73
+ output = [...kept, marker].join(eol);
74
+ while (byteLength(output) > JSON_INVARIANT.maxBytes && kept.length > 0) {
75
+ kept.pop();
76
+ output = [...kept, marker].join(eol);
77
+ }
78
+ return output;
79
+ }
80
+
81
+ function summarizeObject(value: Record<string, unknown>, ctx: ProcessorContext): string {
82
+ const keys = Object.keys(value);
83
+ const lines = [
84
+ "JSON summary",
85
+ "type: object",
86
+ `topLevelKeys (${keys.length}): ${keys.join(", ")}`,
87
+ ];
88
+ for (const key of keys) {
89
+ lines.push(`${key}: ${summarizeValue(value[key])}`);
90
+ }
91
+ return capLines(lines, ctx.eol);
92
+ }
93
+
94
+ function summarizeArray(value: unknown[], ctx: ProcessorContext): string {
95
+ const lines = ["JSON summary", "type: array", `items: ${value.length}`];
96
+ value.slice(0, 5).forEach((item, index) => {
97
+ lines.push(`[${index}]: ${summarizeArrayItem(item)}`);
98
+ });
99
+ return capLines(lines, ctx.eol);
100
+ }
101
+
102
+ export function jsonProcessor(text: string, ctx: ProcessorContext): ProcessorOutput {
103
+ const parsed = tryParseJson(text);
104
+ if (parsed === null) {
105
+ return { text, processorKey: "json", passthrough: true };
106
+ }
107
+
108
+ const summary = Array.isArray(parsed)
109
+ ? summarizeArray(parsed, ctx)
110
+ : summarizeObject(parsed as Record<string, unknown>, ctx);
111
+ return { text: summary, processorKey: "json", passthrough: false };
112
+ }
@@ -0,0 +1,67 @@
1
+ import type { ProcessorContext, ProcessorInvariant, ProcessorOutput } from "./types.js";
2
+
3
+ const encoder = new TextEncoder();
4
+
5
+ export const K8S_INVARIANT: ProcessorInvariant = {
6
+ key: "k8s",
7
+ maxBytes: 8192,
8
+ preserve: ["get headers", "resource status", "describe name/status/events", "last 20 log lines"],
9
+ };
10
+
11
+ function byteLength(text: string): number {
12
+ return encoder.encode(text).byteLength;
13
+ }
14
+
15
+ function normalizeEol(text: string, eol: ProcessorContext["eol"]): string {
16
+ return text.replace(/\r?\n/g, eol);
17
+ }
18
+
19
+ function capLines(lines: string[], eol: ProcessorContext["eol"]): string {
20
+ let output = lines.join(eol);
21
+ if (byteLength(output) <= K8S_INVARIANT.maxBytes) return output;
22
+ const marker = `[...k8s processor omitted lines to stay under ${K8S_INVARIANT.maxBytes} bytes...]`;
23
+ const kept: string[] = [];
24
+ for (const line of lines) {
25
+ const candidate = [...kept, line, marker].join(eol);
26
+ if (byteLength(candidate) > K8S_INVARIANT.maxBytes) break;
27
+ kept.push(line);
28
+ }
29
+ return [...kept, marker].join(eol);
30
+ }
31
+
32
+ function isGetTable(lines: string[]): boolean {
33
+ return lines.some((line) => /\bNAME\b/.test(line) && /\bSTATUS\b/.test(line));
34
+ }
35
+
36
+ function isDescribe(lines: string[]): boolean {
37
+ return lines.some((line) => line.startsWith("Name:")) && lines.some((line) => line.startsWith("Status:"));
38
+ }
39
+
40
+ function compressDescribe(lines: string[], eol: ProcessorContext["eol"]): string {
41
+ const keep = new Set<number>();
42
+ const eventsIndex = lines.findIndex((line) => line.startsWith("Events:"));
43
+ lines.forEach((line, index) => {
44
+ if (/^(Name|Namespace|Status):/.test(line)) keep.add(index);
45
+ if (eventsIndex >= 0 && index >= eventsIndex) keep.add(index);
46
+ });
47
+ return capLines([...keep].sort((a, b) => a - b).map((index) => lines[index]), eol);
48
+ }
49
+
50
+ export function k8sProcessor(text: string, ctx: ProcessorContext): ProcessorOutput {
51
+ const normalized = normalizeEol(text, ctx.eol);
52
+ const lines = normalized.split(ctx.eol).filter((line) => line.length > 0);
53
+
54
+ let compressed: string | null = null;
55
+ if (isGetTable(lines)) {
56
+ compressed = capLines(lines, ctx.eol);
57
+ } else if (isDescribe(lines)) {
58
+ compressed = compressDescribe(lines, ctx.eol);
59
+ } else if (lines.length > 20) {
60
+ compressed = capLines(lines.slice(-20), ctx.eol);
61
+ }
62
+
63
+ if (compressed === null) {
64
+ return { text, processorKey: "k8s", passthrough: true };
65
+ }
66
+ return { text: compressed, processorKey: "k8s", passthrough: false };
67
+ }
@@ -0,0 +1,67 @@
1
+ import type { ProcessorContext, ProcessorInvariant, ProcessorOutput } from "./types.js";
2
+
3
+ const encoder = new TextEncoder();
4
+
5
+ export const LINT_INVARIANT: ProcessorInvariant = {
6
+ key: "lint",
7
+ maxBytes: 8192,
8
+ preserve: ["file/line/column", "severity", "rule name", "final tally"],
9
+ };
10
+
11
+ const DIAGNOSTIC_RE = /(?:\S+\.tsx?:\d+:\d+|^\s*\d+:\d+\s+(?:error|warning)|\b(?:error|warning)\b|\[warn\]|✖|⚠|\b(?:problems|Found \d+ error|Code style issues)\b)/i;
12
+
13
+ function byteLength(text: string): number {
14
+ return encoder.encode(text).byteLength;
15
+ }
16
+
17
+ function normalizeEol(text: string, eol: ProcessorContext["eol"]): string {
18
+ return text.replace(/\r?\n/g, eol);
19
+ }
20
+
21
+ function hasDiagnostics(text: string): boolean {
22
+ return DIAGNOSTIC_RE.test(text);
23
+ }
24
+
25
+ function isImportant(line: string): boolean {
26
+ return DIAGNOSTIC_RE.test(line)
27
+ || /^\S.*\.tsx?$/.test(line)
28
+ || /^Checked \d+ files/i.test(line);
29
+ }
30
+
31
+ function capLines(lines: string[], eol: ProcessorContext["eol"]): string {
32
+ let output = lines.join(eol);
33
+ if (byteLength(output) <= LINT_INVARIANT.maxBytes) return output;
34
+
35
+ const marker = `[...lint processor omitted lines to stay under ${LINT_INVARIANT.maxBytes} bytes...]`;
36
+ const kept: string[] = [];
37
+ for (const line of lines) {
38
+ const candidate = [...kept, line, marker].join(eol);
39
+ if (byteLength(candidate) > LINT_INVARIANT.maxBytes) break;
40
+ kept.push(line);
41
+ }
42
+ output = [...kept, marker].join(eol);
43
+ while (byteLength(output) > LINT_INVARIANT.maxBytes && kept.length > 0) {
44
+ kept.pop();
45
+ output = [...kept, marker].join(eol);
46
+ }
47
+ return output;
48
+ }
49
+
50
+ export function lintProcessor(text: string, ctx: ProcessorContext): ProcessorOutput {
51
+ if (!hasDiagnostics(text)) {
52
+ return { text, processorKey: "lint", passthrough: true };
53
+ }
54
+
55
+ const normalized = normalizeEol(text, ctx.eol);
56
+ const lines = normalized.split(ctx.eol);
57
+ const keep = new Set<number>();
58
+ for (let index = 0; index < lines.length; index += 1) {
59
+ if (!isImportant(lines[index])) continue;
60
+ keep.add(index);
61
+ if (index > 0 && /^\S/.test(lines[index - 1])) keep.add(index - 1);
62
+ if (index + 1 < lines.length && /^\s+(?:✖|⚠|\d|[A-Z])/.test(lines[index + 1])) keep.add(index + 1);
63
+ }
64
+
65
+ const selected = [...keep].sort((a, b) => a - b).map((index) => lines[index]);
66
+ return { text: capLines(selected.length > 0 ? selected : lines, ctx.eol), processorKey: "lint", passthrough: false };
67
+ }
@@ -0,0 +1,86 @@
1
+ import type { ProcessorContext, ProcessorInvariant, ProcessorOutput } from "./types.js";
2
+
3
+ const encoder = new TextEncoder();
4
+
5
+ export const LOG_INVARIANT: ProcessorInvariant = {
6
+ key: "log",
7
+ maxBytes: 8192,
8
+ preserve: [
9
+ "last 30 timestamped lines",
10
+ "up to 20 ERROR/FATAL/PANIC lines",
11
+ "prompt-injection-shaped log lines verbatim",
12
+ "EOL style",
13
+ ],
14
+ };
15
+
16
+ const TIMESTAMP_RE = /^(?:\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?|[A-Z][a-z]{2}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}|\[\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\])/;
17
+ const IMPORTANT_RE = /\b(?:ERROR|FATAL|PANIC)\b|IGNORE PREVIOUS INSTRUCTIONS|system prompt|reveal secrets/i;
18
+
19
+ function byteLength(text: string): number {
20
+ return encoder.encode(text).byteLength;
21
+ }
22
+
23
+ function normalizeEol(text: string, eol: ProcessorContext["eol"]): string {
24
+ return text.replace(/\r?\n/g, eol);
25
+ }
26
+
27
+ function nonBlankLines(text: string): string[] {
28
+ return text.split(/\r?\n/).filter((line) => line.trim().length > 0);
29
+ }
30
+
31
+ function isTimestamped(line: string): boolean {
32
+ return TIMESTAMP_RE.test(line);
33
+ }
34
+
35
+ export function logContentSniff(text: string): boolean {
36
+ const lines = nonBlankLines(text);
37
+ if (lines.length === 0) return false;
38
+ const timestamped = lines.filter(isTimestamped).length;
39
+ return timestamped / lines.length >= 0.8;
40
+ }
41
+
42
+ function capLines(lines: string[], eol: ProcessorContext["eol"]): string {
43
+ let output = lines.join(eol);
44
+ if (byteLength(output) <= LOG_INVARIANT.maxBytes) return output;
45
+
46
+ const marker = `[...log processor omitted lines to stay under ${LOG_INVARIANT.maxBytes} bytes...]`;
47
+ const kept: string[] = [];
48
+ for (const line of lines) {
49
+ const candidate = [...kept, line, marker].join(eol);
50
+ if (byteLength(candidate) > LOG_INVARIANT.maxBytes) break;
51
+ kept.push(line);
52
+ }
53
+ output = [...kept, marker].join(eol);
54
+ while (byteLength(output) > LOG_INVARIANT.maxBytes && kept.length > 0) {
55
+ kept.pop();
56
+ output = [...kept, marker].join(eol);
57
+ }
58
+ return output;
59
+ }
60
+
61
+ export function logProcessor(text: string, ctx: ProcessorContext): ProcessorOutput {
62
+ if (!logContentSniff(text)) {
63
+ return { text, processorKey: "log", passthrough: true };
64
+ }
65
+
66
+ const normalized = normalizeEol(text, ctx.eol);
67
+ const lines = normalized.split(ctx.eol).filter((line) => line.trim().length > 0);
68
+ const timestamped = lines
69
+ .map((line, index) => ({ line, index }))
70
+ .filter((entry) => isTimestamped(entry.line));
71
+ const lastTimestamped = timestamped.slice(-30);
72
+ const important = lines
73
+ .map((line, index) => ({ line, index }))
74
+ .filter((entry) => IMPORTANT_RE.test(entry.line))
75
+ .slice(0, 20);
76
+
77
+ const keep = new Map<number, string>();
78
+ for (const entry of important) keep.set(entry.index, entry.line);
79
+ for (const entry of lastTimestamped) keep.set(entry.index, entry.line);
80
+
81
+ const selected = [...keep.entries()]
82
+ .sort(([a], [b]) => a - b)
83
+ .map(([, line]) => line);
84
+
85
+ return { text: capLines(selected, ctx.eol), processorKey: "log", passthrough: false };
86
+ }