@open-mercato/cezar 0.1.5-pr506.357

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 (264) hide show
  1. package/README.md +573 -0
  2. package/dist/config.d.ts +144 -0
  3. package/dist/config.js +106 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/core/agent-runner.d.ts +169 -0
  6. package/dist/core/agent-runner.js +23 -0
  7. package/dist/core/agent-runner.js.map +1 -0
  8. package/dist/core/backend-detect.d.ts +16 -0
  9. package/dist/core/backend-detect.js +118 -0
  10. package/dist/core/backend-detect.js.map +1 -0
  11. package/dist/core/claude-cli-runner.d.ts +56 -0
  12. package/dist/core/claude-cli-runner.js +416 -0
  13. package/dist/core/claude-cli-runner.js.map +1 -0
  14. package/dist/core/claude-ui-mapper.d.ts +71 -0
  15. package/dist/core/claude-ui-mapper.js +567 -0
  16. package/dist/core/claude-ui-mapper.js.map +1 -0
  17. package/dist/core/codex-app-server-runner.d.ts +29 -0
  18. package/dist/core/codex-app-server-runner.js +489 -0
  19. package/dist/core/codex-app-server-runner.js.map +1 -0
  20. package/dist/core/codex-ui-mapper.d.ts +63 -0
  21. package/dist/core/codex-ui-mapper.js +491 -0
  22. package/dist/core/codex-ui-mapper.js.map +1 -0
  23. package/dist/core/ndjson.d.ts +6 -0
  24. package/dist/core/ndjson.js +23 -0
  25. package/dist/core/ndjson.js.map +1 -0
  26. package/dist/core/opencode-server-runner.d.ts +30 -0
  27. package/dist/core/opencode-server-runner.js +488 -0
  28. package/dist/core/opencode-server-runner.js.map +1 -0
  29. package/dist/core/opencode-ui-mapper.d.ts +105 -0
  30. package/dist/core/opencode-ui-mapper.js +588 -0
  31. package/dist/core/opencode-ui-mapper.js.map +1 -0
  32. package/dist/core/process-usage.d.ts +63 -0
  33. package/dist/core/process-usage.js +181 -0
  34. package/dist/core/process-usage.js.map +1 -0
  35. package/dist/core/runner-factory.d.ts +8 -0
  36. package/dist/core/runner-factory.js +22 -0
  37. package/dist/core/runner-factory.js.map +1 -0
  38. package/dist/core/tool-display.d.ts +30 -0
  39. package/dist/core/tool-display.js +156 -0
  40. package/dist/core/tool-display.js.map +1 -0
  41. package/dist/core/ui-events.d.ts +308 -0
  42. package/dist/core/ui-events.js +25 -0
  43. package/dist/core/ui-events.js.map +1 -0
  44. package/dist/core/usage.d.ts +11 -0
  45. package/dist/core/usage.js +15 -0
  46. package/dist/core/usage.js.map +1 -0
  47. package/dist/git-worktree.d.ts +88 -0
  48. package/dist/git-worktree.js +297 -0
  49. package/dist/git-worktree.js.map +1 -0
  50. package/dist/handoff.d.ts +62 -0
  51. package/dist/handoff.js +147 -0
  52. package/dist/handoff.js.map +1 -0
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.js +466 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/install-as-command.d.ts +39 -0
  57. package/dist/install-as-command.js +51 -0
  58. package/dist/install-as-command.js.map +1 -0
  59. package/dist/pack-check.d.ts +17 -0
  60. package/dist/pack-check.js +27 -0
  61. package/dist/pack-check.js.map +1 -0
  62. package/dist/paths.d.ts +23 -0
  63. package/dist/paths.js +34 -0
  64. package/dist/paths.js.map +1 -0
  65. package/dist/planner.d.ts +17 -0
  66. package/dist/planner.js +248 -0
  67. package/dist/planner.js.map +1 -0
  68. package/dist/release/snapshot.d.ts +64 -0
  69. package/dist/release/snapshot.js +75 -0
  70. package/dist/release/snapshot.js.map +1 -0
  71. package/dist/release/stable.d.ts +46 -0
  72. package/dist/release/stable.js +57 -0
  73. package/dist/release/stable.js.map +1 -0
  74. package/dist/runs/auto-name.d.ts +75 -0
  75. package/dist/runs/auto-name.js +168 -0
  76. package/dist/runs/auto-name.js.map +1 -0
  77. package/dist/runs/retention.d.ts +60 -0
  78. package/dist/runs/retention.js +86 -0
  79. package/dist/runs/retention.js.map +1 -0
  80. package/dist/runs/review-gate.d.ts +18 -0
  81. package/dist/runs/review-gate.js +21 -0
  82. package/dist/runs/review-gate.js.map +1 -0
  83. package/dist/runs/store.d.ts +408 -0
  84. package/dist/runs/store.js +526 -0
  85. package/dist/runs/store.js.map +1 -0
  86. package/dist/runs/task-refs.d.ts +24 -0
  87. package/dist/runs/task-refs.js +74 -0
  88. package/dist/runs/task-refs.js.map +1 -0
  89. package/dist/runs/ui-event-sink.d.ts +81 -0
  90. package/dist/runs/ui-event-sink.js +210 -0
  91. package/dist/runs/ui-event-sink.js.map +1 -0
  92. package/dist/server/capabilities.d.ts +33 -0
  93. package/dist/server/capabilities.js +43 -0
  94. package/dist/server/capabilities.js.map +1 -0
  95. package/dist/server/forge/github.d.ts +70 -0
  96. package/dist/server/forge/github.js +375 -0
  97. package/dist/server/forge/github.js.map +1 -0
  98. package/dist/server/forge/index.d.ts +24 -0
  99. package/dist/server/forge/index.js +45 -0
  100. package/dist/server/forge/index.js.map +1 -0
  101. package/dist/server/forge/types.d.ts +80 -0
  102. package/dist/server/forge/types.js +2 -0
  103. package/dist/server/forge/types.js.map +1 -0
  104. package/dist/server/git-changes.d.ts +195 -0
  105. package/dist/server/git-changes.js +497 -0
  106. package/dist/server/git-changes.js.map +1 -0
  107. package/dist/server/git.d.ts +26 -0
  108. package/dist/server/git.js +103 -0
  109. package/dist/server/git.js.map +1 -0
  110. package/dist/server/github.d.ts +8 -0
  111. package/dist/server/github.js +8 -0
  112. package/dist/server/github.js.map +1 -0
  113. package/dist/server/launch-key.d.ts +7 -0
  114. package/dist/server/launch-key.js +33 -0
  115. package/dist/server/launch-key.js.map +1 -0
  116. package/dist/server/open-in-app.d.ts +51 -0
  117. package/dist/server/open-in-app.js +189 -0
  118. package/dist/server/open-in-app.js.map +1 -0
  119. package/dist/server/open-in-terminal.d.ts +25 -0
  120. package/dist/server/open-in-terminal.js +120 -0
  121. package/dist/server/open-in-terminal.js.map +1 -0
  122. package/dist/server/pr.d.ts +7 -0
  123. package/dist/server/pr.js +7 -0
  124. package/dist/server/pr.js.map +1 -0
  125. package/dist/server/server.d.ts +41 -0
  126. package/dist/server/server.js +1390 -0
  127. package/dist/server/server.js.map +1 -0
  128. package/dist/server/static-ui.d.ts +49 -0
  129. package/dist/server/static-ui.js +92 -0
  130. package/dist/server/static-ui.js.map +1 -0
  131. package/dist/server/wsl.d.ts +25 -0
  132. package/dist/server/wsl.js +92 -0
  133. package/dist/server/wsl.js.map +1 -0
  134. package/dist/server-install/engine.d.ts +33 -0
  135. package/dist/server-install/engine.js +314 -0
  136. package/dist/server-install/engine.js.map +1 -0
  137. package/dist/server-install/platforms/macosx-ngrok.d.ts +6 -0
  138. package/dist/server-install/platforms/macosx-ngrok.js +345 -0
  139. package/dist/server-install/platforms/macosx-ngrok.js.map +1 -0
  140. package/dist/server-install/platforms/ubuntu-vps.d.ts +39 -0
  141. package/dist/server-install/platforms/ubuntu-vps.js +746 -0
  142. package/dist/server-install/platforms/ubuntu-vps.js.map +1 -0
  143. package/dist/server-install/state.d.ts +26 -0
  144. package/dist/server-install/state.js +125 -0
  145. package/dist/server-install/state.js.map +1 -0
  146. package/dist/server-install/steps.d.ts +109 -0
  147. package/dist/server-install/steps.js +330 -0
  148. package/dist/server-install/steps.js.map +1 -0
  149. package/dist/server-install/strategies.d.ts +4 -0
  150. package/dist/server-install/strategies.js +20 -0
  151. package/dist/server-install/strategies.js.map +1 -0
  152. package/dist/server-install/types.d.ts +597 -0
  153. package/dist/server-install/types.js +98 -0
  154. package/dist/server-install/types.js.map +1 -0
  155. package/dist/server-install/ui.d.ts +42 -0
  156. package/dist/server-install/ui.js +130 -0
  157. package/dist/server-install/ui.js.map +1 -0
  158. package/dist/skills-banner.d.ts +21 -0
  159. package/dist/skills-banner.js +40 -0
  160. package/dist/skills-banner.js.map +1 -0
  161. package/dist/skills-remote.d.ts +35 -0
  162. package/dist/skills-remote.js +266 -0
  163. package/dist/skills-remote.js.map +1 -0
  164. package/dist/skills.d.ts +45 -0
  165. package/dist/skills.js +182 -0
  166. package/dist/skills.js.map +1 -0
  167. package/dist/todos.d.ts +63 -0
  168. package/dist/todos.js +168 -0
  169. package/dist/todos.js.map +1 -0
  170. package/dist/ui-state.d.ts +12 -0
  171. package/dist/ui-state.js +25 -0
  172. package/dist/ui-state.js.map +1 -0
  173. package/dist/update-check.d.ts +12 -0
  174. package/dist/update-check.js +41 -0
  175. package/dist/update-check.js.map +1 -0
  176. package/dist/workflows/load.d.ts +15 -0
  177. package/dist/workflows/load.js +56 -0
  178. package/dist/workflows/load.js.map +1 -0
  179. package/dist/workflows/run.d.ts +281 -0
  180. package/dist/workflows/run.js +1511 -0
  181. package/dist/workflows/run.js.map +1 -0
  182. package/dist/workflows/types.d.ts +307 -0
  183. package/dist/workflows/types.js +171 -0
  184. package/dist/workflows/types.js.map +1 -0
  185. package/package.json +90 -0
  186. package/scripts/check-pack.mjs +46 -0
  187. package/scripts/dev.mjs +74 -0
  188. package/scripts/install-as-command.mjs +120 -0
  189. package/scripts/mock-claude.mjs +310 -0
  190. package/scripts/release-snapshot.mjs +130 -0
  191. package/scripts/release.mjs +119 -0
  192. package/scripts/test-process-usage.mjs +47 -0
  193. package/web/dist/assets/arrow-left-BE957rJs.js +1 -0
  194. package/web/dist/assets/bundle-mjs-DLPpRFyl.js +1 -0
  195. package/web/dist/assets/centered-state-BddhEEtO.js +43 -0
  196. package/web/dist/assets/chunk-BO2N2NFS-f5igTtpB.js +131 -0
  197. package/web/dist/assets/command-CjhFbABo.js +1 -0
  198. package/web/dist/assets/compare-variants-Cs52IAEG.js +1 -0
  199. package/web/dist/assets/core-BuMqAF_1.js +12 -0
  200. package/web/dist/assets/css-BsVw1vtW.js +1 -0
  201. package/web/dist/assets/dialog-DFvXTkFv.js +1 -0
  202. package/web/dist/assets/diff-view-C9b8AITq.js +4 -0
  203. package/web/dist/assets/diff-woXpYk--.js +1 -0
  204. package/web/dist/assets/dist-FBs4wmIY.js +1 -0
  205. package/web/dist/assets/dist-o_kVMXjg.js +1 -0
  206. package/web/dist/assets/engine-javascript-FQvzRocB.js +141 -0
  207. package/web/dist/assets/file-BVuztAOd.js +1 -0
  208. package/web/dist/assets/folder-0pJ1NZ_S.js +1 -0
  209. package/web/dist/assets/git-pull-request-DJkAMT0f.js +1 -0
  210. package/web/dist/assets/git-toolbar-CwhQdx3J.js +1 -0
  211. package/web/dist/assets/github-D0rAX4-r.js +1 -0
  212. package/web/dist/assets/go-rLFTqkRN.js +1 -0
  213. package/web/dist/assets/highlighted-body-OFNGDK62-DGHHie_q.js +1 -0
  214. package/web/dist/assets/highlighter-BKVEsACy.js +3 -0
  215. package/web/dist/assets/html-BY76lXLH.js +1 -0
  216. package/web/dist/assets/index-CEkI7QxN.css +2 -0
  217. package/web/dist/assets/index-CO2lH2et.js +11 -0
  218. package/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  219. package/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  220. package/web/dist/assets/javascript-BgS3c2Ky.js +1 -0
  221. package/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  222. package/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  223. package/web/dist/assets/json-qhed-kSA.js +1 -0
  224. package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  225. package/web/dist/assets/jsx-Bz0zcwM4.js +1 -0
  226. package/web/dist/assets/lib-CNGa3Dig.js +1 -0
  227. package/web/dist/assets/loader-circle-BV3DO9mE.js +1 -0
  228. package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  229. package/web/dist/assets/markdown-D4-ADgH9.js +1 -0
  230. package/web/dist/assets/mermaid-GHXKKRXX-DM2VsGBG.js +1 -0
  231. package/web/dist/assets/open-mercato-toBr6SOa.svg +11 -0
  232. package/web/dist/assets/prompt-templates-C7ptKvDd.js +15 -0
  233. package/web/dist/assets/python-gzcpVVnB.js +1 -0
  234. package/web/dist/assets/react-dom-Ddtik4qM.js +1 -0
  235. package/web/dist/assets/refresh-cw-DCa-Rlwz.js +1 -0
  236. package/web/dist/assets/repo-git-DIhryGDx.js +1 -0
  237. package/web/dist/assets/run-diff-Dyow8B1q.js +3 -0
  238. package/web/dist/assets/run-header-jx9-18b8.js +1 -0
  239. package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
  240. package/web/dist/assets/search-x-CoCyl7zT.js +1 -0
  241. package/web/dist/assets/shellscript-CLZ0U2zV.js +1 -0
  242. package/web/dist/assets/skill-detail-DB7x_FQp.js +1 -0
  243. package/web/dist/assets/skills-98vCroU8.js +1 -0
  244. package/web/dist/assets/skills-BKIoqD6K.js +1 -0
  245. package/web/dist/assets/sql-BsFa4tDR.js +1 -0
  246. package/web/dist/assets/square-terminal-B6PZX4Qp.js +1 -0
  247. package/web/dist/assets/tab-link-C5--YMQd.js +1 -0
  248. package/web/dist/assets/task-changes-ejxPGVJw.js +1 -0
  249. package/web/dist/assets/task-commits-C6GO5qT-.js +1 -0
  250. package/web/dist/assets/task-files-CatpCBXa.js +2 -0
  251. package/web/dist/assets/task-thread-BMbXDM3d.js +5 -0
  252. package/web/dist/assets/tasks-table-dgezlCcg.js +1 -0
  253. package/web/dist/assets/trash-2-CDtxmmc8.js +1 -0
  254. package/web/dist/assets/triangle-alert-6su4Js5O.js +1 -0
  255. package/web/dist/assets/tsx-udAQXfEw.js +1 -0
  256. package/web/dist/assets/typescript-bsJCZSQ-.js +1 -0
  257. package/web/dist/assets/upload-Bp7hQxll.js +1 -0
  258. package/web/dist/assets/use-desktop-CvTwojOJ.js +3 -0
  259. package/web/dist/assets/utils-rQGbOrwc.js +1 -0
  260. package/web/dist/assets/workflows-Dy2c6790.js +11 -0
  261. package/web/dist/assets/x-Br_jIDBw.js +1 -0
  262. package/web/dist/assets/yaml-rwi0_p6S.js +1 -0
  263. package/web/dist/index.html +70 -0
  264. package/web/open-mercato.svg +11 -0
@@ -0,0 +1,75 @@
1
+ import { type TaskRefs } from './task-refs.js';
2
+ /**
3
+ * The one-shot LLM namer (spec 2026-07-17-task-auto-naming): turns a task's
4
+ * INTENT — skill + arguments + prompt + PR/issue context, never the agent's
5
+ * streamed words — into a short `<number>: <gerund phrase>` display title with
6
+ * structured, regex-cross-checked PR/issue numbers. Mirrors the planner (spec
7
+ * 008): `[cez-namer]` dry-run marker, strict JSON, never blocks, never fails a
8
+ * run. This module is the pure half; the runner call lives in `generateRunName`.
9
+ */
10
+ export declare const NAMER_TIMEOUT_MS = 20000;
11
+ /** Title budget in code points — the tasks table shows ~20-25 chars, 40 is the hard cap. */
12
+ export declare const TITLE_MAX = 40;
13
+ /**
14
+ * Master switch for ALL LLM naming (creation-time and live refresh):
15
+ * `CEZ_AUTONAME=0` kills it outright; under `CEZ_DRY_RUN=1` naming is off by
16
+ * default too (the mock's canned title would REPLACE honest heuristic titles
17
+ * in demos and e2e) unless `CEZ_AUTONAME=1` forces it — the hook the dry-run
18
+ * naming tests use.
19
+ */
20
+ export declare function autoNamingActive(env?: NodeJS.ProcessEnv): boolean;
21
+ /**
22
+ * Live title updates switch (owner decision on PR #479 — deliberately ON by
23
+ * default, deviating from the cost-opt-in house rule; cost is bounded by the
24
+ * cheap `namerModel`, one call per turn end, and the skip conditions in
25
+ * `RunManager.recordTurnEnd`). Precedence: `config.liveTitleUpdates` (the
26
+ * Settings toggle) wins over the `CEZ_TITLE_UPDATES` env default (`'0'` = off)
27
+ * wins over the built-in ON.
28
+ */
29
+ export declare function liveTitleUpdatesEnabled(config: {
30
+ liveTitleUpdates?: boolean;
31
+ }, env?: NodeJS.ProcessEnv): boolean;
32
+ export declare const NAMER_SYSTEM_PROMPT: string;
33
+ export interface NamerContext {
34
+ task: string;
35
+ skillName?: string;
36
+ skillDescription?: string;
37
+ /** Live refresh only (spec step 3): the just-finished turn's text. */
38
+ turnText?: string;
39
+ /** Live refresh only: current `git diff --shortstat` line. */
40
+ diffStat?: string;
41
+ }
42
+ export interface NameResult {
43
+ titleSummary: string;
44
+ prNumber?: number;
45
+ issueNumber?: number;
46
+ }
47
+ /** The `[cez-namer]` marker lets the CEZ_DRY_RUN mock recognize a naming call. */
48
+ export declare function buildNamerPrompt(ctx: NamerContext): string;
49
+ /**
50
+ * Anti-hallucination cross-check: the model's `pr`/`issue` is accepted only
51
+ * when the regex layer agrees or the number literally occurs in the task text;
52
+ * on any disagreement the regex wins. The regex's own explicit findings always
53
+ * carry through, so a lying model can add nothing and remove nothing.
54
+ */
55
+ export declare function crossCheckRefs(raw: {
56
+ pr?: number;
57
+ issue?: number;
58
+ }, task: string, refs: TaskRefs): {
59
+ prNumber?: number;
60
+ issueNumber?: number;
61
+ };
62
+ /** Enforce the title contract regardless of what the model produced. */
63
+ export declare function postValidateTitle(title: string, refNumber?: number): string;
64
+ /**
65
+ * The one-shot runner call: never throws, never blocks a run — every failure
66
+ * path (runner error, timeout, junk twice) answers null and the caller keeps
67
+ * the heuristic title. `namerModel` is a Claude alias, so it is passed only
68
+ * when the namer runs on Claude (the `plannerModel` precedent).
69
+ */
70
+ export declare function generateRunName(repoRoot: string, ctx: NamerContext): Promise<NameResult | null>;
71
+ /**
72
+ * Raw model text → validated NameResult, or null when the answer is junk
73
+ * (caller retries once, then keeps the heuristic title).
74
+ */
75
+ export declare function composeNameResult(rawText: string, ctx: NamerContext): NameResult | null;
@@ -0,0 +1,168 @@
1
+ import { z } from 'zod';
2
+ import { loadConfig } from '../config.js';
3
+ import { createRunner } from '../core/runner-factory.js';
4
+ import { parseStructured } from '../planner.js';
5
+ import { extractTaskRefs, refineTaskRefs, titleRefNumber } from './task-refs.js';
6
+ /**
7
+ * The one-shot LLM namer (spec 2026-07-17-task-auto-naming): turns a task's
8
+ * INTENT — skill + arguments + prompt + PR/issue context, never the agent's
9
+ * streamed words — into a short `<number>: <gerund phrase>` display title with
10
+ * structured, regex-cross-checked PR/issue numbers. Mirrors the planner (spec
11
+ * 008): `[cez-namer]` dry-run marker, strict JSON, never blocks, never fails a
12
+ * run. This module is the pure half; the runner call lives in `generateRunName`.
13
+ */
14
+ export const NAMER_TIMEOUT_MS = 20_000;
15
+ /** Title budget in code points — the tasks table shows ~20-25 chars, 40 is the hard cap. */
16
+ export const TITLE_MAX = 40;
17
+ /**
18
+ * Master switch for ALL LLM naming (creation-time and live refresh):
19
+ * `CEZ_AUTONAME=0` kills it outright; under `CEZ_DRY_RUN=1` naming is off by
20
+ * default too (the mock's canned title would REPLACE honest heuristic titles
21
+ * in demos and e2e) unless `CEZ_AUTONAME=1` forces it — the hook the dry-run
22
+ * naming tests use.
23
+ */
24
+ export function autoNamingActive(env = process.env) {
25
+ if (env.CEZ_AUTONAME === '0')
26
+ return false;
27
+ if (env.CEZ_DRY_RUN === '1' && env.CEZ_AUTONAME !== '1')
28
+ return false;
29
+ return true;
30
+ }
31
+ /**
32
+ * Live title updates switch (owner decision on PR #479 — deliberately ON by
33
+ * default, deviating from the cost-opt-in house rule; cost is bounded by the
34
+ * cheap `namerModel`, one call per turn end, and the skip conditions in
35
+ * `RunManager.recordTurnEnd`). Precedence: `config.liveTitleUpdates` (the
36
+ * Settings toggle) wins over the `CEZ_TITLE_UPDATES` env default (`'0'` = off)
37
+ * wins over the built-in ON.
38
+ */
39
+ export function liveTitleUpdatesEnabled(config, env = process.env) {
40
+ if (config.liveTitleUpdates !== undefined)
41
+ return config.liveTitleUpdates;
42
+ return env.CEZ_TITLE_UPDATES !== '0';
43
+ }
44
+ export const NAMER_SYSTEM_PROMPT = 'You are a task-naming assistant for an AI coding agent cockpit. Respond with ONLY a JSON object: ' +
45
+ '{"title": string, "pr"?: number, "issue"?: number}. Rules: "title" is a terse lowercase gerund phrase ' +
46
+ 'naming the task INTENT (e.g. "implementing cr fixes", "verifying pr ui"), at most 40 characters, no ' +
47
+ 'trailing period, and NEVER a restatement of an assistant reply. When the task is about a GitHub pull ' +
48
+ 'request or issue, set "pr" or "issue" to that number and do NOT repeat the number inside the title ' +
49
+ 'text. Never invent a number that is not in the task.';
50
+ const namerResponseSchema = z.object({
51
+ title: z.string().min(1),
52
+ pr: z.number().int().positive().optional(),
53
+ issue: z.number().int().positive().optional(),
54
+ });
55
+ /** The `[cez-namer]` marker lets the CEZ_DRY_RUN mock recognize a naming call. */
56
+ export function buildNamerPrompt(ctx) {
57
+ const refs = advisoryRefs(ctx);
58
+ const lines = [
59
+ '[cez-namer] Name this task.',
60
+ '',
61
+ ...(ctx.skillName ? [`Selected skill: /${ctx.skillName}`] : []),
62
+ ...(ctx.skillDescription ? [`Skill description: ${ctx.skillDescription}`] : []),
63
+ 'Task:',
64
+ clip(ctx.task, 500),
65
+ ];
66
+ if (refs.length)
67
+ lines.push('', `References found programmatically (advisory): ${refs.join(', ')}`);
68
+ if (ctx.turnText)
69
+ lines.push('', 'Latest progress (context only — do not quote it):', clip(ctx.turnText, 400));
70
+ if (ctx.diffStat)
71
+ lines.push('', `Change size so far: ${ctx.diffStat}`);
72
+ return lines.join('\n');
73
+ }
74
+ function advisoryRefs(ctx) {
75
+ const refs = refineTaskRefs(extractTaskRefs(ctx.task), ctx.skillName);
76
+ const out = [];
77
+ if (refs.prNumber !== undefined)
78
+ out.push(`pr ${refs.prNumber}`);
79
+ if (refs.issueNumber !== undefined)
80
+ out.push(`issue ${refs.issueNumber}`);
81
+ if (refs.ambiguousNumber !== undefined)
82
+ out.push(`number ${refs.ambiguousNumber} (kind unknown)`);
83
+ return out;
84
+ }
85
+ function clip(text, max) {
86
+ const chars = [...text.trim()];
87
+ return chars.length > max ? `${chars.slice(0, max).join('')}…` : chars.join('');
88
+ }
89
+ /**
90
+ * Anti-hallucination cross-check: the model's `pr`/`issue` is accepted only
91
+ * when the regex layer agrees or the number literally occurs in the task text;
92
+ * on any disagreement the regex wins. The regex's own explicit findings always
93
+ * carry through, so a lying model can add nothing and remove nothing.
94
+ */
95
+ export function crossCheckRefs(raw, task, refs) {
96
+ const inTask = (n) => n !== undefined && new RegExp(`(^|\\D)${n}(\\D|$)`).test(task);
97
+ const claimedPr = refs.prNumber ?? (raw.pr !== undefined && (inTask(raw.pr) || refs.ambiguousNumber === raw.pr) ? raw.pr : undefined);
98
+ const claimedIssue = refs.issueNumber ?? (raw.issue !== undefined && (inTask(raw.issue) || refs.ambiguousNumber === raw.issue) ? raw.issue : undefined);
99
+ return {
100
+ ...(claimedPr !== undefined ? { prNumber: claimedPr } : {}),
101
+ ...(claimedIssue !== undefined ? { issueNumber: claimedIssue } : {}),
102
+ };
103
+ }
104
+ /** Enforce the title contract regardless of what the model produced. */
105
+ export function postValidateTitle(title, refNumber) {
106
+ let t = title.trim().replace(/\.+$/, '').replace(/\s+/g, ' ');
107
+ // The number leads; strip a model-repeated number from the phrase first.
108
+ if (refNumber !== undefined) {
109
+ t = t.replace(new RegExp(`^#?${refNumber}\\s*[:—-]?\\s*`), '');
110
+ }
111
+ if (t)
112
+ t = t[0]?.toLowerCase() + t.slice(1);
113
+ const chars = [...t];
114
+ if (chars.length > TITLE_MAX)
115
+ t = `${chars.slice(0, TITLE_MAX - 1).join('').trimEnd()}…`;
116
+ return refNumber !== undefined ? `${refNumber}: ${t}`.trim().replace(/:\s*$/, ':') : t;
117
+ }
118
+ /**
119
+ * The one-shot runner call: never throws, never blocks a run — every failure
120
+ * path (runner error, timeout, junk twice) answers null and the caller keeps
121
+ * the heuristic title. `namerModel` is a Claude alias, so it is passed only
122
+ * when the namer runs on Claude (the `plannerModel` precedent).
123
+ */
124
+ export async function generateRunName(repoRoot, ctx) {
125
+ try {
126
+ const config = await loadConfig(repoRoot);
127
+ const runner = createRunner(config.defaultRunner);
128
+ const model = config.defaultRunner === 'claude' ? config.namerModel : undefined;
129
+ for (let attempt = 0; attempt < 2; attempt++) {
130
+ const result = await runner.run({
131
+ systemPrompt: NAMER_SYSTEM_PROMPT,
132
+ userPrompt: buildNamerPrompt(ctx),
133
+ cwd: repoRoot,
134
+ allowedTools: [],
135
+ model,
136
+ timeoutMs: NAMER_TIMEOUT_MS,
137
+ // Shadow the argv-capture hook: dry-run tests capture the AGENT's argv
138
+ // through CEZ_MOCK_ARGS_FILE; the namer's bookkeeping call must not
139
+ // interleave lines into that file.
140
+ env: { CEZ_MOCK_ARGS_FILE: '' },
141
+ });
142
+ const composed = composeNameResult(result.text, ctx);
143
+ if (composed)
144
+ return composed;
145
+ }
146
+ }
147
+ catch {
148
+ // runner unavailable / crashed — the heuristic title stays
149
+ }
150
+ return null;
151
+ }
152
+ /**
153
+ * Raw model text → validated NameResult, or null when the answer is junk
154
+ * (caller retries once, then keeps the heuristic title).
155
+ */
156
+ export function composeNameResult(rawText, ctx) {
157
+ const parsed = parseStructured(rawText, namerResponseSchema);
158
+ if (!parsed)
159
+ return null;
160
+ const refs = refineTaskRefs(extractTaskRefs(ctx.task), ctx.skillName);
161
+ const checked = crossCheckRefs(parsed, ctx.task, refs);
162
+ const refNumber = checked.prNumber ?? checked.issueNumber ?? titleRefNumber(refs);
163
+ const titleSummary = postValidateTitle(parsed.title, refNumber);
164
+ if (!titleSummary || titleSummary === String(refNumber ?? ''))
165
+ return null;
166
+ return { titleSummary, ...checked };
167
+ }
168
+ //# sourceMappingURL=auto-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-name.js","sourceRoot":"","sources":["../../src/runs/auto-name.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import type { RunRecord } from './store.js';
2
+ /** A run is reclaimable when it is finished, still has a materialized worktree
3
+ * directory, and has not already been reclaimed. */
4
+ export declare function isReclaimable(run: RunRecord): boolean;
5
+ /**
6
+ * Given every run and the keep-count `keep`, return the ids of the finished
7
+ * worktrees whose *directory* should be reclaimed: keep the `keep`
8
+ * most-recently-finished reclaimable worktrees, reclaim the rest.
9
+ *
10
+ * `keep === 0` means "unlimited — never auto-reclaim" and returns `[]`.
11
+ * Pure: no I/O, no mutation of the input.
12
+ */
13
+ export declare function selectReclaimableWorktrees(runs: readonly RunRecord[], keep: number): string[];
14
+ /** The slice of the runs store the enforcer needs. Kept structural so the
15
+ * enforcer stays easy to test and never imports the concrete store. */
16
+ export interface RetentionStore {
17
+ listRuns(): RunRecord[];
18
+ updateRun(id: string, patch: {
19
+ worktreeReclaimedAt?: string;
20
+ }): unknown;
21
+ }
22
+ /** The slice of the store the re-materializer needs. */
23
+ export interface RematerializeStore {
24
+ getRun(id: string): RunRecord | undefined;
25
+ updateRun(id: string, patch: {
26
+ worktreeReclaimedAt?: string;
27
+ }): unknown;
28
+ }
29
+ /**
30
+ * If retention (#483) reclaimed this run's worktree — branch kept, directory
31
+ * gone, `worktreeReclaimedAt` stamped — re-materialize the directory (via the
32
+ * idempotent `createWorktree`, which reattaches the surviving `cez/<id8>`
33
+ * branch) and CLEAR the stamp. Called on the resume/continue path so a resumed
34
+ * run regains its isolated tree and becomes eligible for retention again;
35
+ * without it the run would keep a directory on disk while staying invisible to
36
+ * the enforcer forever (a leak). Returns true when it re-materialized.
37
+ * Best-effort: never throws (the caller falls back to the repo root).
38
+ */
39
+ export declare function rematerializeReclaimedWorktree(repoRoot: string, store: RematerializeStore, runId: string): Promise<boolean>;
40
+ /**
41
+ * Enforce the retention budget: reclaim the *directory* of every over-limit
42
+ * finished worktree (branch kept via `removeWorktree` without the branch arg),
43
+ * stamping `worktreeReclaimedAt` on each run actually reclaimed. Returns the
44
+ * reclaimed run ids (for logging/SSE).
45
+ *
46
+ * Never throws (helper discipline). `removeWorktree` is best-effort and does not
47
+ * report failure, so a run is stamped only once its directory is confirmed gone
48
+ * — a locked/permission failure leaves the stamp unset so the next pass retries.
49
+ * Idempotent under races: `removeWorktree` is `--force` + `prune` and a repeated
50
+ * stamp is harmless.
51
+ */
52
+ export interface ReclaimOptions {
53
+ /** Timestamp source for the stamp — injectable for deterministic tests. */
54
+ now?: () => string;
55
+ /** Directory reclaimer — defaults to the real `removeWorktree` (branch kept).
56
+ * Injectable so tests can exercise the "removal failed" branch without brittle
57
+ * filesystem-permission tricks. */
58
+ remove?: (repoRoot: string, worktreePath: string) => Promise<void>;
59
+ }
60
+ export declare function reclaimWorktrees(repoRoot: string, store: RetentionStore, keep: number, opts?: ReclaimOptions): Promise<string[]>;
@@ -0,0 +1,86 @@
1
+ // Count-based worktree retention (#483). A busy cockpit leaves one full repo
2
+ // checkout per finished task under `.ai/cezar/worktrees/<runId>`; nothing bounds
3
+ // the total, so disk saturates. This module decides *which* finished worktrees
4
+ // to reclaim (directory only — the `cez/<id8>` branch is kept, so the work stays
5
+ // recoverable) and the thin I/O enforcer that performs the reclaim. The selector
6
+ // is pure and unit-testable; the enforcer never throws (helper discipline).
7
+ import { existsSync } from 'node:fs';
8
+ import { createWorktree, removeWorktree } from '../git-worktree.js';
9
+ /** The "finished" status set — mirrors `RunStore.archiveFinished`. A run at the
10
+ * `review` gate is deliberately excluded: it still needs its worktree to render
11
+ * the diff and open a draft PR, so reclaiming it would break the gate. */
12
+ const FINISHED = new Set(['done', 'failed', 'cancelled']);
13
+ /** Recency key for retention ordering: when a run finished, falling back to when
14
+ * it was created (a finished run should always have `finishedAt`, but old
15
+ * records may not). Lexicographic compare is correct for ISO-8601 timestamps. */
16
+ function recencyKey(run) {
17
+ return run.finishedAt ?? run.createdAt;
18
+ }
19
+ /** A run is reclaimable when it is finished, still has a materialized worktree
20
+ * directory, and has not already been reclaimed. */
21
+ export function isReclaimable(run) {
22
+ return FINISHED.has(run.status) && !!run.worktreePath && !run.worktreeReclaimedAt;
23
+ }
24
+ /**
25
+ * Given every run and the keep-count `keep`, return the ids of the finished
26
+ * worktrees whose *directory* should be reclaimed: keep the `keep`
27
+ * most-recently-finished reclaimable worktrees, reclaim the rest.
28
+ *
29
+ * `keep === 0` means "unlimited — never auto-reclaim" and returns `[]`.
30
+ * Pure: no I/O, no mutation of the input.
31
+ */
32
+ export function selectReclaimableWorktrees(runs, keep) {
33
+ if (!Number.isFinite(keep) || keep <= 0)
34
+ return [];
35
+ const reclaimable = runs
36
+ .filter(isReclaimable)
37
+ .sort((a, b) => (recencyKey(a) < recencyKey(b) ? 1 : recencyKey(a) > recencyKey(b) ? -1 : 0));
38
+ return reclaimable.slice(keep).map((r) => r.id);
39
+ }
40
+ /**
41
+ * If retention (#483) reclaimed this run's worktree — branch kept, directory
42
+ * gone, `worktreeReclaimedAt` stamped — re-materialize the directory (via the
43
+ * idempotent `createWorktree`, which reattaches the surviving `cez/<id8>`
44
+ * branch) and CLEAR the stamp. Called on the resume/continue path so a resumed
45
+ * run regains its isolated tree and becomes eligible for retention again;
46
+ * without it the run would keep a directory on disk while staying invisible to
47
+ * the enforcer forever (a leak). Returns true when it re-materialized.
48
+ * Best-effort: never throws (the caller falls back to the repo root).
49
+ */
50
+ export async function rematerializeReclaimedWorktree(repoRoot, store, runId) {
51
+ const run = store.getRun(runId);
52
+ if (!run?.worktreePath || !run.worktreeReclaimedAt || existsSync(run.worktreePath))
53
+ return false;
54
+ try {
55
+ await createWorktree(repoRoot, runId, run.baseBranch ?? 'HEAD');
56
+ store.updateRun(runId, { worktreeReclaimedAt: undefined });
57
+ return true;
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ export async function reclaimWorktrees(repoRoot, store, keep, opts = {}) {
64
+ const now = opts.now ?? (() => new Date().toISOString());
65
+ const remove = opts.remove ?? ((root, path) => removeWorktree(root, path)); // branch kept
66
+ const runs = store.listRuns();
67
+ const byId = new Map(runs.map((r) => [r.id, r]));
68
+ const reclaimed = [];
69
+ for (const id of selectReclaimableWorktrees(runs, keep)) {
70
+ const run = byId.get(id);
71
+ if (!run?.worktreePath)
72
+ continue;
73
+ try {
74
+ await remove(repoRoot, run.worktreePath);
75
+ if (existsSync(run.worktreePath))
76
+ continue; // reclaim failed; retry next pass
77
+ store.updateRun(id, { worktreeReclaimedAt: now() });
78
+ reclaimed.push(id);
79
+ }
80
+ catch {
81
+ // best-effort: never let retention crash a terminal transition or startup.
82
+ }
83
+ }
84
+ return reclaimed;
85
+ }
86
+ //# sourceMappingURL=retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/runs/retention.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Optional diff-first review gate (#489, spec `2026-07-18-optional-review-gate`).
3
+ *
4
+ * The review gate (spec 009) used to fire for EVERY successful run whose worktree
5
+ * held changes — parking it at `review` until a human accepts. This resolver makes
6
+ * that opt-in. It mirrors `liveTitleUpdatesEnabled` (`src/runs/auto-name.ts`) but
7
+ * with the OPPOSITE default: OFF unless explicitly turned on.
8
+ *
9
+ * Precedence: the `config.reviewGate` Settings toggle wins when set; otherwise the
10
+ * `CEZ_REVIEW_GATE` env decides — enabled ONLY by the exact string `'1'` (anything
11
+ * else, including `'true'`/`'yes'`/unset, is off); otherwise off.
12
+ *
13
+ * Autonomy is applied by the callers, not here: a run parks at `review` only when
14
+ * `worktreeHasDiff && reviewGateEnabled(config) && !run.autonomous`.
15
+ */
16
+ export declare function reviewGateEnabled(config: {
17
+ reviewGate?: boolean;
18
+ }, env?: NodeJS.ProcessEnv): boolean;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Optional diff-first review gate (#489, spec `2026-07-18-optional-review-gate`).
3
+ *
4
+ * The review gate (spec 009) used to fire for EVERY successful run whose worktree
5
+ * held changes — parking it at `review` until a human accepts. This resolver makes
6
+ * that opt-in. It mirrors `liveTitleUpdatesEnabled` (`src/runs/auto-name.ts`) but
7
+ * with the OPPOSITE default: OFF unless explicitly turned on.
8
+ *
9
+ * Precedence: the `config.reviewGate` Settings toggle wins when set; otherwise the
10
+ * `CEZ_REVIEW_GATE` env decides — enabled ONLY by the exact string `'1'` (anything
11
+ * else, including `'true'`/`'yes'`/unset, is off); otherwise off.
12
+ *
13
+ * Autonomy is applied by the callers, not here: a run parks at `review` only when
14
+ * `worktreeHasDiff && reviewGateEnabled(config) && !run.autonomous`.
15
+ */
16
+ export function reviewGateEnabled(config, env = process.env) {
17
+ if (config.reviewGate !== undefined)
18
+ return config.reviewGate;
19
+ return env.CEZ_REVIEW_GATE === '1';
20
+ }
21
+ //# sourceMappingURL=review-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-gate.js","sourceRoot":"","sources":["../../src/runs/review-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAgC,EAChC,MAAyB,OAAO,CAAC,GAAG;IAEpC,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,UAAU,CAAC;IAC9D,OAAO,GAAG,CAAC,eAAe,KAAK,GAAG,CAAC;AACrC,CAAC"}