supipowers 1.5.3 → 2.0.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 (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 +129 -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 +264 -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
@@ -1,4 +1,9 @@
1
1
  // src/context-mode/compressor.ts
2
+ import { canonicalToolName } from "./tool-name.js";
3
+ import type { ContextModeProcessorsConfig } from "../types.js";
4
+ import type { ProcessorKey } from "./metrics-store.js";
5
+ import { processorKeyForTool } from "./processor-keys.js";
6
+ import { lookupProcessor } from "./processors/registry.js";
2
7
 
3
8
  interface ToolResultEventLike {
4
9
  toolName: string;
@@ -12,13 +17,53 @@ interface ToolResultEventResult {
12
17
  content?: Array<{ type: string; text: string }>;
13
18
  }
14
19
 
20
+ export interface RunEmissionPipelineOptions {
21
+ processors?: ContextModeProcessorsConfig;
22
+ }
23
+
24
+ export interface PipelineResult {
25
+ result: ToolResultEventResult | undefined;
26
+ processorKey: ProcessorKey;
27
+ }
28
+
15
29
  const BASH_HEAD_LINES = 5;
16
30
  const BASH_TAIL_LINES = 10;
17
31
  const READ_HEAD_LINES = 80;
18
32
  const READ_TAIL_LINES = 30;
19
- const GREP_MAX_MATCHES = 10;
33
+ const SEARCH_MAX_MATCHES = 10;
20
34
  const FIND_MAX_PATHS = 20;
21
35
 
36
+
37
+ const READ_PATH_SELECTOR_RE = /:(?:raw|\d+(?:[-+]\d+)?|L\d+(?:-L?\d+|\+L?\d+)?)$/i;
38
+
39
+ function getReadPath(input: Record<string, unknown>): string | null {
40
+ const path = input.path ?? input.file_path;
41
+ return typeof path === "string" && path.length > 0 ? path : null;
42
+ }
43
+
44
+ function hasEmbeddedReadSelector(input: Record<string, unknown>): boolean {
45
+ const path = getReadPath(input);
46
+ return path != null && READ_PATH_SELECTOR_RE.test(path);
47
+ }
48
+
49
+ function formatReadSelectorHint(input: Record<string, unknown>, start: number, end: number): string {
50
+ const path = getReadPath(input);
51
+ const selector = path?.startsWith("http://") || path?.startsWith("https://")
52
+ ? `L${start}-L${end}`
53
+ : `${start}-${end}`;
54
+ return path
55
+ ? `read(path="${path}:${selector}")`
56
+ : `read(path="<path>:${selector}")`;
57
+ }
58
+ /**
59
+ * OMP's `shellMinimizer` already shrinks bash output and appends this footer
60
+ * pointing at the full bytes. When we see it, supipowers must NOT re-compress —
61
+ * doing so would either drop the pointer or double-truncate already-trimmed text.
62
+ * The artifact id is recoverable via `read artifact://<id>`.
63
+ */
64
+ export const OMP_MINIMIZER_FOOTER_RE = /(?:^|\n)\[raw output: artifact:\/\/[a-zA-Z0-9_-]+\]\s*$/;
65
+
66
+
22
67
  /** Measure total byte length of text content entries */
23
68
  function measureTextBytes(content: Array<{ type: string; text?: string }>): number {
24
69
  let total = 0;
@@ -43,6 +88,22 @@ function getCombinedText(content: Array<{ type: string; text?: string }>): strin
43
88
  .join("\n");
44
89
  }
45
90
 
91
+ function exitCodeFromDetails(details: unknown): number | null {
92
+ if (details && typeof details === "object" && "exitCode" in details) {
93
+ const exitCode = (details as { exitCode?: unknown }).exitCode;
94
+ return typeof exitCode === "number" ? exitCode : null;
95
+ }
96
+ return null;
97
+ }
98
+
99
+ function eolOf(text: string): "\n" | "\r\n" {
100
+ return text.includes("\r\n") ? "\r\n" : "\n";
101
+ }
102
+
103
+ function wrapText(text: string): ToolResultEventResult {
104
+ return { content: [{ type: "text", text }] };
105
+ }
106
+
46
107
  /** Compress bash tool output */
47
108
  function compressBash(text: string, details: unknown): string | undefined {
48
109
  const exitCode =
@@ -50,6 +111,9 @@ function compressBash(text: string, details: unknown): string | undefined {
50
111
  ? (details as { exitCode: number }).exitCode
51
112
  : 0;
52
113
 
114
+ // Already minimized by OMP — pass through to preserve the artifact pointer.
115
+ if (OMP_MINIMIZER_FOOTER_RE.test(text)) return undefined;
116
+
53
117
  // Non-zero exit: keep full output for debugging
54
118
  if (exitCode !== 0) return undefined;
55
119
 
@@ -71,8 +135,11 @@ function compressBash(text: string, details: unknown): string | undefined {
71
135
 
72
136
  /** Compress read tool output — head+tail with hashline preservation */
73
137
  function compressRead(text: string, input: Record<string, unknown>): string | undefined {
74
- // Scoped reads (offset/limit/sel) are already targeted pass through
75
- if (input.offset != null || input.limit != null || input.sel != null) return undefined;
138
+ // Already minimized by OMP (e.g. when reading a `bash-original` artifact back). Pass through.
139
+ if (OMP_MINIMIZER_FOOTER_RE.test(text)) return undefined;
140
+
141
+ // Scoped reads (offset/limit/path selector) are already targeted — pass through
142
+ if (input.offset != null || input.limit != null || hasEmbeddedReadSelector(input)) return undefined;
76
143
 
77
144
  const lines = text.split("\n");
78
145
  const totalLines = lines.length;
@@ -84,26 +151,27 @@ function compressRead(text: string, input: Record<string, unknown>): string | un
84
151
  const omittedStart = READ_HEAD_LINES + 1;
85
152
  const omittedEnd = totalLines - READ_TAIL_LINES;
86
153
 
154
+ const selectorHint = formatReadSelectorHint(input, omittedStart, omittedEnd);
87
155
  return [
88
156
  ...head,
89
- `[...${omittedEnd - omittedStart + 1} lines omitted. Use read(path, sel="L${omittedStart}-L${omittedEnd}") to view...]`,
157
+ `[...${omittedEnd - omittedStart + 1} lines omitted. Use ${selectorHint} to view...]`,
90
158
  ...tail,
91
159
  ].join("\n");
92
160
  }
93
161
 
94
- /** Compress grep tool output */
95
- function compressGrep(text: string): string | undefined {
162
+ /** Compress search tool output */
163
+ function compressSearch(text: string): string | undefined {
96
164
  const lines = text.split("\n").filter((l) => l.length > 0);
97
165
  const totalMatches = lines.length;
98
166
 
99
- if (totalMatches <= GREP_MAX_MATCHES) return undefined;
167
+ if (totalMatches <= SEARCH_MAX_MATCHES) return undefined;
100
168
 
101
- const kept = lines.slice(0, GREP_MAX_MATCHES);
169
+ const kept = lines.slice(0, SEARCH_MAX_MATCHES);
102
170
  return [
103
- `${totalMatches} matches total, showing first ${GREP_MAX_MATCHES}:`,
171
+ `${totalMatches} matches total, showing first ${SEARCH_MAX_MATCHES}:`,
104
172
  "",
105
173
  ...kept,
106
- `[...compressed: ${totalMatches - GREP_MAX_MATCHES} more matches omitted...]`,
174
+ `[...compressed: ${totalMatches - SEARCH_MAX_MATCHES} more matches omitted...]`,
107
175
  ].join("\n");
108
176
  }
109
177
 
@@ -123,45 +191,82 @@ function compressFind(text: string): string | undefined {
123
191
  ].join("\n");
124
192
  }
125
193
 
126
- /** Compress a tool result if it exceeds the threshold */
127
- export function compressToolResult(
194
+ /** Run the deterministic emission pipeline and report the processor decision. */
195
+ export function runEmissionPipeline(
128
196
  event: ToolResultEventLike,
129
197
  threshold: number,
130
- ): ToolResultEventResult | undefined {
131
- // General rules: pass through errors, non-text content, and small outputs
132
- if (event.isError) return undefined;
133
- if (hasNonTextContent(event.content)) return undefined;
134
- if (measureTextBytes(event.content) <= threshold) return undefined;
198
+ options: RunEmissionPipelineOptions = {},
199
+ ): PipelineResult {
200
+ const canonicalTool = canonicalToolName(event.toolName);
201
+ const nativeProcessorKey = processorKeyForTool(canonicalTool);
202
+ const passthroughProcessorKey: ProcessorKey = nativeProcessorKey === null ? null : "passthrough";
203
+
204
+ // General rules: pass through errors, non-text content, and small outputs.
205
+ if (event.isError) return { result: undefined, processorKey: passthroughProcessorKey };
206
+ if (hasNonTextContent(event.content)) return { result: undefined, processorKey: passthroughProcessorKey };
207
+
208
+ const byteSize = measureTextBytes(event.content);
209
+ if (byteSize <= threshold) {
210
+ return {
211
+ result: undefined,
212
+ processorKey: passthroughProcessorKey,
213
+ };
214
+ }
135
215
 
136
216
  const text = getCombinedText(event.content);
137
- let compressed: string | undefined;
138
217
 
139
- switch (event.toolName) {
218
+ if (canonicalTool === "bash" && OMP_MINIMIZER_FOOTER_RE.test(text)) {
219
+ return { result: undefined, processorKey: "omp-minimizer" };
220
+ }
221
+
222
+ if (canonicalTool === "bash") {
223
+ const match = lookupProcessor(canonicalTool, event.input, text, options);
224
+ if (match) {
225
+ const output = match.processor(text, {
226
+ exitCode: exitCodeFromDetails(event.details),
227
+ eol: eolOf(text),
228
+ });
229
+ if (!output.passthrough) {
230
+ return { result: wrapText(output.text), processorKey: output.processorKey };
231
+ }
232
+ }
233
+ }
234
+
235
+ let compressed: string | undefined;
236
+ switch (canonicalTool) {
140
237
  case "bash":
141
238
  compressed = compressBash(text, event.details);
142
239
  break;
143
240
  case "read":
144
241
  compressed = compressRead(text, event.input);
145
242
  break;
146
- case "grep":
147
- compressed = compressGrep(text);
243
+ case "search":
244
+ compressed = compressSearch(text);
148
245
  break;
149
246
  case "find":
150
247
  compressed = compressFind(text);
151
248
  break;
152
249
  default:
153
- return undefined;
250
+ return { result: undefined, processorKey: null };
154
251
  }
155
252
 
156
- if (!compressed) return undefined;
157
- return { content: [{ type: "text", text: compressed }] };
253
+ if (!compressed) return { result: undefined, processorKey: "passthrough" };
254
+ return { result: wrapText(compressed), processorKey: nativeProcessorKey };
255
+ }
256
+
257
+ /** Compress a tool result if it exceeds the threshold */
258
+ export function compressToolResult(
259
+ event: ToolResultEventLike,
260
+ threshold: number,
261
+ ): ToolResultEventResult | undefined {
262
+ return runEmissionPipeline(event, threshold).result;
158
263
  }
159
264
 
160
265
  /** Summarization prompt templates by tool type */
161
266
  const SUMMARIZE_PROMPTS: Record<string, string> = {
162
267
  bash: "Summarize this command output. Preserve: exit code, key findings, error messages, file paths mentioned. Be concise (under 200 words).",
163
268
  read: "Summarize this file content. Preserve: file structure, key exports/functions, notable patterns. Be concise (under 200 words).",
164
- grep: "Summarize these search results. Preserve: match count, most relevant matches, file distribution. Be concise (under 200 words).",
269
+ search: "Summarize these search results. Preserve: match count, most relevant matches, file distribution. Be concise (under 200 words).",
165
270
  find: "Summarize these file paths. Preserve: directory structure, file count, key patterns. Be concise (under 200 words).",
166
271
  };
167
272
 
@@ -187,7 +292,7 @@ export async function compressToolResultWithLLM(
187
292
 
188
293
  // Above LLM threshold: try LLM summarization
189
294
  try {
190
- const prompt = SUMMARIZE_PROMPTS[event.toolName] ?? "Summarize this output concisely (under 200 words).";
295
+ const prompt = SUMMARIZE_PROMPTS[canonicalToolName(event.toolName)] ?? "Summarize this output concisely (under 200 words).";
191
296
  const summary = await summarize(`${prompt}\n\n${text}`, event.toolName);
192
297
 
193
298
  // Validate: non-empty and reasonably sized
@@ -0,0 +1,108 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { ProcessorKey } from "./metrics-store.js";
3
+
4
+ export interface DedupRecord {
5
+ contentHash: string;
6
+ turnId: number;
7
+ bytes: number;
8
+ tsMonotonic: number;
9
+ }
10
+
11
+ export interface DedupState {
12
+ records: Map<string, DedupRecord>;
13
+ turnCounter: number;
14
+ }
15
+
16
+ interface ToolResultEventResult {
17
+ content?: Array<{ type: string; text: string }>;
18
+ }
19
+
20
+ export interface DedupSubstitutionInput {
21
+ result: ToolResultEventResult | undefined;
22
+ processorKey: ProcessorKey;
23
+ sourceHash: string | null;
24
+ dedupState: DedupState;
25
+ processedBytes: number;
26
+ }
27
+
28
+ export interface DedupSubstitutionResult {
29
+ result: ToolResultEventResult | undefined;
30
+ processorKey: ProcessorKey;
31
+ }
32
+
33
+ export const TTL_TURNS = 10;
34
+
35
+ export function createDedupState(): DedupState {
36
+ return { records: new Map(), turnCounter: 0 };
37
+ }
38
+
39
+ export function combinedTextOf(
40
+ content: Array<{ type: string; text?: string }> | undefined,
41
+ ): string {
42
+ if (!content) return "";
43
+ const textEntries: string[] = [];
44
+ for (const entry of content) {
45
+ if (entry.type === "text" && entry.text) {
46
+ textEntries.push(entry.text);
47
+ }
48
+ }
49
+ return textEntries.join("\n");
50
+ }
51
+
52
+ function contentHashFor(result: ToolResultEventResult): string {
53
+ return createHash("sha256").update(combinedTextOf(result.content)).digest("hex");
54
+ }
55
+
56
+ export function maybeSubstitute(input: DedupSubstitutionInput): DedupSubstitutionResult {
57
+ const { result, processorKey, sourceHash, dedupState, processedBytes } = input;
58
+ if (sourceHash === null || result === undefined) {
59
+ return { result, processorKey };
60
+ }
61
+
62
+ const key = sourceHash;
63
+ const contentHash = contentHashFor(result);
64
+ const existing = dedupState.records.get(key);
65
+ if (!existing || existing.contentHash !== contentHash) {
66
+ const supersedes = existing && existing.contentHash !== contentHash ? existing : null;
67
+ dedupState.turnCounter += 1;
68
+ dedupState.records.set(key, {
69
+ contentHash,
70
+ turnId: dedupState.turnCounter,
71
+ bytes: processedBytes,
72
+ tsMonotonic: dedupState.turnCounter,
73
+ });
74
+ if (supersedes) {
75
+ const annotated = withSupersedesAnnotation(result, supersedes);
76
+ return { result: annotated, processorKey };
77
+ }
78
+ return { result, processorKey };
79
+ }
80
+
81
+ if (dedupState.turnCounter - existing.tsMonotonic >= TTL_TURNS) {
82
+ dedupState.turnCounter += 1;
83
+ dedupState.records.set(key, {
84
+ contentHash,
85
+ turnId: dedupState.turnCounter,
86
+ bytes: processedBytes,
87
+ tsMonotonic: dedupState.turnCounter,
88
+ });
89
+ return { result, processorKey };
90
+ }
91
+
92
+ const placeholderText = `[…dedup: same as turn ${existing.turnId} (${existing.bytes} B); processor=${processorKey}]`;
93
+ return {
94
+ result: { content: [{ type: "text", text: placeholderText }] },
95
+ processorKey: "dedup",
96
+ };
97
+ }
98
+
99
+ function withSupersedesAnnotation(
100
+ result: ToolResultEventResult,
101
+ prior: DedupRecord,
102
+ ): ToolResultEventResult {
103
+ const banner = `[\u2026 supersedes turn ${prior.turnId} (was ${prior.bytes} B); old bytes remain in transcript history]`;
104
+ const text = combinedTextOf(result.content);
105
+ return {
106
+ content: [{ type: "text", text: text.length > 0 ? `${banner}\n${text}` : banner }],
107
+ };
108
+ }
@@ -10,15 +10,41 @@ export interface ContextModeStatus {
10
10
  ctxIndex: boolean;
11
11
  ctxSearch: boolean;
12
12
  ctxFetchAndIndex: boolean;
13
+ ctxOpenCached: boolean;
14
+ ctxStats: boolean;
15
+ ctxPurge: boolean;
16
+ ctxRepomap: boolean;
17
+ ctxSymbol: boolean;
13
18
  };
14
19
  }
15
20
 
16
21
  /**
17
- * Context-mode tools are native (registered by this extension), so they are
18
- * always available when the extension is loaded. The interface is preserved for
19
- * backward compatibility with hooks/routing consumers.
22
+ * Return active context-mode tool status. When activeTools is supplied, it is
23
+ * treated as the current model-visible tool set. Without it, keep the legacy
24
+ * registered-tool fallback for compatibility with older call sites.
20
25
  */
21
- export function detectContextMode(_activeTools?: string[]): ContextModeStatus {
26
+ export function detectContextMode(activeTools?: string[]): ContextModeStatus {
27
+ if (activeTools) {
28
+ const active = new Set(activeTools);
29
+ const tools = {
30
+ ctxExecute: active.has("ctx_execute"),
31
+ ctxBatchExecute: active.has("ctx_batch_execute"),
32
+ ctxExecuteFile: active.has("ctx_execute_file"),
33
+ ctxIndex: active.has("ctx_index"),
34
+ ctxSearch: active.has("ctx_search"),
35
+ ctxFetchAndIndex: active.has("ctx_fetch_and_index"),
36
+ ctxOpenCached: active.has("ctx_open_cached"),
37
+ ctxStats: active.has("ctx_stats"),
38
+ ctxPurge: active.has("ctx_purge"),
39
+ ctxRepomap: active.has("ctx_repomap"),
40
+ ctxSymbol: active.has("ctx_symbol"),
41
+ };
42
+ return {
43
+ available: Object.values(tools).some(Boolean),
44
+ tools,
45
+ };
46
+ }
47
+
22
48
  return {
23
49
  available: true,
24
50
  tools: {
@@ -28,6 +54,11 @@ export function detectContextMode(_activeTools?: string[]): ContextModeStatus {
28
54
  ctxIndex: true,
29
55
  ctxSearch: true,
30
56
  ctxFetchAndIndex: true,
57
+ ctxOpenCached: true,
58
+ ctxStats: true,
59
+ ctxPurge: true,
60
+ ctxRepomap: true,
61
+ ctxSymbol: true,
31
62
  },
32
63
  };
33
64
  }
@@ -1,6 +1,7 @@
1
1
  // src/context-mode/event-extractor.ts
2
2
  import { PRIORITY } from "./event-store.js";
3
3
  import type { EventCategory, EventPriority, TrackedEvent } from "./event-store.js";
4
+ import { canonicalToolName } from "./tool-name.js";
4
5
 
5
6
  type Event = Omit<TrackedEvent, "id" | "dataHash">;
6
7
 
@@ -53,6 +54,7 @@ export function extractEvents(
53
54
  details: unknown;
54
55
  },
55
56
  sessionId: string,
57
+ sourceHash?: string | null,
56
58
  ): Event[] {
57
59
  const events: Event[] = [];
58
60
  const text = getTextContent(event.content);
@@ -65,7 +67,7 @@ export function extractEvents(
65
67
  }, PRIORITY.critical, "tool_result"));
66
68
  }
67
69
 
68
- switch (event.toolName) {
70
+ switch (canonicalToolName(event.toolName)) {
69
71
  case "bash":
70
72
  extractBash(events, event, sessionId, text);
71
73
  break;
@@ -77,21 +79,20 @@ export function extractEvents(
77
79
  if (readPath.includes("/skills/")) {
78
80
  events.push(makeEvent(sessionId, "skill", { path: readPath }, PRIORITY.medium, "tool_result"));
79
81
  }
80
- extractFile(events, event, sessionId, "read");
82
+ extractFile(events, event, sessionId, "read", PRIORITY.medium, sourceHash);
81
83
  break;
82
84
  }
83
85
  case "edit":
84
- extractFile(events, event, sessionId, "edit", PRIORITY.high);
86
+ extractFile(events, event, sessionId, "edit", PRIORITY.high, sourceHash);
85
87
  break;
86
88
  case "write":
87
- extractFile(events, event, sessionId, "write", PRIORITY.high);
88
- break;
89
- case "grep":
90
- extractFile(events, event, sessionId, "search");
91
- break;
92
- case "find":
93
- extractFile(events, event, sessionId, "find");
89
+ extractFile(events, event, sessionId, "write", PRIORITY.high, sourceHash);
94
90
  break;
91
+ // `search` and `find` are intentionally not extracted: their `path`
92
+ // input is usually a directory or a glob, not a real file the caller
93
+ // touched. The downstream snapshot builder only consumes
94
+ // `op === "edit"|"write"|"read"` file events, so emitting here would
95
+ // (a) be silently dropped and (b) waste the 200-event read window.
95
96
  case "todo_write":
96
97
  events.push(makeEvent(sessionId, "task", {
97
98
  input: event.input,
@@ -102,7 +103,7 @@ export function extractEvents(
102
103
  events.push(makeEvent(sessionId, "mcp", {
103
104
  tool: event.toolName,
104
105
  }, PRIORITY.low, "tool_result"));
105
- } else if (event.toolName === "task" || event.toolName === "sub_agent") {
106
+ } else if (canonicalToolName(event.toolName) === "task" || event.toolName === "sub_agent") {
106
107
  events.push(makeEvent(sessionId, "subagent", {
107
108
  toolName: event.toolName,
108
109
  input: event.input,
@@ -171,9 +172,10 @@ function extractFile(
171
172
  sessionId: string,
172
173
  op: string,
173
174
  priority: EventPriority = PRIORITY.medium,
175
+ sourceHash?: string | null,
174
176
  ): void {
175
177
  const path = typeof event.input.path === "string" ? event.input.path : "unknown";
176
- events.push(makeEvent(sessionId, "file", { op, path }, priority, "tool_result"));
178
+ events.push(makeEvent(sessionId, "file", { op, path, ...(sourceHash ? { sourceHash } : {}) }, priority, "tool_result"));
177
179
  }
178
180
 
179
181
  /** Extract events from a user prompt (called from before_agent_start handler) */