cloving 1.0.1 → 1.2.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 (309) hide show
  1. package/README.md +251 -20
  2. package/assets/watch-fonts/fira-code-OFL.md +93 -0
  3. package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
  4. package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
  5. package/assets/watch-fonts/heebo-OFL.md +93 -0
  6. package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
  7. package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
  8. package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
  9. package/assets/watch-fonts/manifest.json +112 -0
  10. package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
  11. package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
  12. package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
  13. package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
  14. package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
  15. package/dist/src/authority/commands.d.ts +1 -0
  16. package/dist/src/authority/commands.js +33 -4
  17. package/dist/src/authority/dispatch-decision.d.ts +1 -0
  18. package/dist/src/authority/dispatch-decision.js +7 -4
  19. package/dist/src/authority/events.js +2 -2
  20. package/dist/src/authority/handoff-chain.d.ts +24 -0
  21. package/dist/src/authority/handoff-chain.js +46 -0
  22. package/dist/src/authority/reconciliation.d.ts +3 -0
  23. package/dist/src/authority/reconciliation.js +21 -5
  24. package/dist/src/authority/transitions.d.ts +1 -1
  25. package/dist/src/authority/transitions.js +5 -0
  26. package/dist/src/broker/actor/authority-queries.d.ts +4 -0
  27. package/dist/src/broker/actor/authority-queries.js +51 -2
  28. package/dist/src/broker/actor/authority-session.d.ts +2 -0
  29. package/dist/src/broker/actor/authority-session.js +3 -4
  30. package/dist/src/broker/actor/delivery-intents.d.ts +1 -0
  31. package/dist/src/broker/actor/effect-delivery.js +9 -0
  32. package/dist/src/broker/authority-admission.js +13 -5
  33. package/dist/src/chats/analysis-batches.d.ts +42 -0
  34. package/dist/src/chats/analysis-batches.js +97 -0
  35. package/dist/src/chats/analysis-checkpoint.d.ts +29 -0
  36. package/dist/src/chats/analysis-checkpoint.js +31 -0
  37. package/dist/src/chats/analysis-contract.d.ts +80 -0
  38. package/dist/src/chats/analysis-contract.js +16 -0
  39. package/dist/src/chats/analysis-data.d.ts +3 -0
  40. package/dist/src/chats/analysis-data.js +85 -0
  41. package/dist/src/chats/analysis-input.d.ts +18 -0
  42. package/dist/src/chats/analysis-input.js +97 -0
  43. package/dist/src/chats/analysis-links.d.ts +2 -0
  44. package/dist/src/chats/analysis-links.js +25 -0
  45. package/dist/src/chats/analysis-merge.d.ts +16 -0
  46. package/dist/src/chats/analysis-merge.js +134 -0
  47. package/dist/src/chats/analysis-prompt.d.ts +18 -0
  48. package/dist/src/chats/analysis-prompt.js +42 -0
  49. package/dist/src/chats/analysis-proposal.d.ts +4 -0
  50. package/dist/src/chats/analysis-proposal.js +41 -0
  51. package/dist/src/chats/analysis-provenance.d.ts +4 -0
  52. package/dist/src/chats/analysis-provenance.js +99 -0
  53. package/dist/src/chats/analysis-shape.d.ts +4 -0
  54. package/dist/src/chats/analysis-shape.js +235 -0
  55. package/dist/src/chats/analysis.d.ts +3 -0
  56. package/dist/src/chats/analysis.js +42 -0
  57. package/dist/src/chats/corpus-input.d.ts +5 -0
  58. package/dist/src/chats/corpus-input.js +288 -0
  59. package/dist/src/chats/destination.js +3 -1
  60. package/dist/src/chats/import.d.ts +8 -6
  61. package/dist/src/chats/import.js +195 -23
  62. package/dist/src/chats/input-file.d.ts +3 -0
  63. package/dist/src/chats/input-file.js +32 -0
  64. package/dist/src/chats/normalize.d.ts +11 -2
  65. package/dist/src/chats/normalize.js +18 -2
  66. package/dist/src/chats/previous-import.d.ts +5 -0
  67. package/dist/src/chats/previous-import.js +34 -0
  68. package/dist/src/chats/reader.d.ts +7 -1
  69. package/dist/src/chats/reader.js +46 -15
  70. package/dist/src/chats/redact-import.d.ts +14 -0
  71. package/dist/src/chats/redact-import.js +141 -0
  72. package/dist/src/chats/redaction.d.ts +41 -0
  73. package/dist/src/chats/redaction.js +161 -0
  74. package/dist/src/chats/reference-mapping.d.ts +16 -0
  75. package/dist/src/chats/reference-mapping.js +153 -0
  76. package/dist/src/chats/revision-input.d.ts +3 -0
  77. package/dist/src/chats/revision-input.js +180 -0
  78. package/dist/src/chats/revision.d.ts +48 -0
  79. package/dist/src/chats/revision.js +94 -0
  80. package/dist/src/chats/selection.d.ts +25 -0
  81. package/dist/src/chats/selection.js +109 -0
  82. package/dist/src/chats/source.d.ts +23 -0
  83. package/dist/src/chats/source.js +135 -0
  84. package/dist/src/chats/structure.d.ts +57 -0
  85. package/dist/src/chats/structure.js +216 -0
  86. package/dist/src/chats/workflow-generation-projection.d.ts +8 -0
  87. package/dist/src/chats/workflow-generation-projection.js +75 -0
  88. package/dist/src/chats/workflow-generation-source.d.ts +25 -0
  89. package/dist/src/chats/workflow-generation-source.js +124 -0
  90. package/dist/src/chats/workflow-generation-text.d.ts +23 -0
  91. package/dist/src/chats/workflow-generation-text.js +74 -0
  92. package/dist/src/chats/workflow-generation.d.ts +69 -0
  93. package/dist/src/chats/workflow-generation.js +142 -0
  94. package/dist/src/cli/arguments.d.ts +8 -0
  95. package/dist/src/cli/arguments.js +94 -13
  96. package/dist/src/cli/client.d.ts +1 -0
  97. package/dist/src/cli/client.js +38 -1
  98. package/dist/src/cli/commands.d.ts +3 -0
  99. package/dist/src/cli/commands.js +29 -11
  100. package/dist/src/cli/main.d.ts +2 -1
  101. package/dist/src/cli/main.js +61 -14
  102. package/dist/src/cli/render.d.ts +3 -0
  103. package/dist/src/cli/render.js +122 -7
  104. package/dist/src/cli/usage-render.d.ts +3 -0
  105. package/dist/src/cli/usage-render.js +28 -0
  106. package/dist/src/cli/usage.d.ts +3 -2
  107. package/dist/src/cli/usage.js +42 -7
  108. package/dist/src/cli/watch-actions.d.ts +10 -0
  109. package/dist/src/cli/watch-actions.js +58 -0
  110. package/dist/src/cli/watch-board-view.d.ts +44 -1
  111. package/dist/src/cli/watch-board-view.js +137 -18
  112. package/dist/src/cli/watch-board.d.ts +7 -1
  113. package/dist/src/cli/watch-board.js +48 -11
  114. package/dist/src/cli/watch-card-client.d.ts +1 -0
  115. package/dist/src/cli/watch-card-client.js +44 -0
  116. package/dist/src/cli/watch-card-forms.d.ts +2 -0
  117. package/dist/src/cli/watch-card-forms.js +33 -0
  118. package/dist/src/cli/watch-card-reply.d.ts +5 -0
  119. package/dist/src/cli/watch-card-reply.js +34 -0
  120. package/dist/src/cli/watch-card-view.d.ts +40 -0
  121. package/dist/src/cli/watch-card-view.js +86 -0
  122. package/dist/src/cli/watch-client.d.ts +1 -0
  123. package/dist/src/cli/watch-client.js +122 -0
  124. package/dist/src/cli/watch-model.d.ts +10 -4
  125. package/dist/src/cli/watch-model.js +61 -67
  126. package/dist/src/cli/watch-page.d.ts +1 -1
  127. package/dist/src/cli/watch-page.js +89 -86
  128. package/dist/src/cli/watch-phases.js +3 -3
  129. package/dist/src/cli/watch-pr-board.d.ts +3 -0
  130. package/dist/src/cli/watch-pr-board.js +26 -0
  131. package/dist/src/cli/watch-pr-command.d.ts +18 -0
  132. package/dist/src/cli/watch-pr-command.js +74 -0
  133. package/dist/src/cli/watch-pr-identity.d.ts +32 -0
  134. package/dist/src/cli/watch-pr-identity.js +87 -0
  135. package/dist/src/cli/watch-pr-observer.d.ts +20 -0
  136. package/dist/src/cli/watch-pr-observer.js +150 -0
  137. package/dist/src/cli/watch-pr-settings.d.ts +8 -0
  138. package/dist/src/cli/watch-pr-settings.js +47 -0
  139. package/dist/src/cli/watch-pull-request.d.ts +10 -0
  140. package/dist/src/cli/watch-pull-request.js +130 -0
  141. package/dist/src/cli/watch-reads.d.ts +7 -1
  142. package/dist/src/cli/watch-reads.js +44 -18
  143. package/dist/src/cli/watch-render.d.ts +29 -2
  144. package/dist/src/cli/watch-render.js +11 -3
  145. package/dist/src/cli/watch-run-input.d.ts +14 -0
  146. package/dist/src/cli/watch-run-input.js +49 -0
  147. package/dist/src/cli/watch-server.d.ts +3 -0
  148. package/dist/src/cli/watch-server.js +77 -8
  149. package/dist/src/cli/watch-start-view.d.ts +18 -0
  150. package/dist/src/cli/watch-start-view.js +77 -0
  151. package/dist/src/cli/watch-turn.d.ts +1 -0
  152. package/dist/src/cli/watch-turn.js +8 -3
  153. package/dist/src/cli/watch-usage.d.ts +6 -0
  154. package/dist/src/cli/watch-usage.js +65 -0
  155. package/dist/src/cli/watch-view.d.ts +22 -0
  156. package/dist/src/cli/watch-view.js +111 -19
  157. package/dist/src/cli.js +48 -6
  158. package/dist/src/daemon/analysis-launch.d.ts +5 -0
  159. package/dist/src/daemon/analysis-launch.js +81 -0
  160. package/dist/src/daemon/analysis-token.d.ts +6 -0
  161. package/dist/src/daemon/analysis-token.js +88 -0
  162. package/dist/src/daemon/analysis-worker.d.ts +20 -0
  163. package/dist/src/daemon/analysis-worker.js +271 -0
  164. package/dist/src/daemon/chats-batch-binding.d.ts +23 -0
  165. package/dist/src/daemon/chats-batch-binding.js +99 -0
  166. package/dist/src/daemon/chats-capture.d.ts +18 -0
  167. package/dist/src/daemon/chats-capture.js +194 -0
  168. package/dist/src/daemon/chats-checkpoints.d.ts +41 -0
  169. package/dist/src/daemon/chats-checkpoints.js +186 -0
  170. package/dist/src/daemon/chats-files.d.ts +42 -0
  171. package/dist/src/daemon/chats-files.js +252 -0
  172. package/dist/src/daemon/chats-follow-up.d.ts +38 -0
  173. package/dist/src/daemon/chats-follow-up.js +180 -0
  174. package/dist/src/daemon/chats-question.d.ts +15 -0
  175. package/dist/src/daemon/chats-question.js +93 -0
  176. package/dist/src/daemon/chats-worker.d.ts +13 -0
  177. package/dist/src/daemon/chats-worker.js +201 -0
  178. package/dist/src/daemon/claude-turn.d.ts +11 -0
  179. package/dist/src/daemon/claude-turn.js +96 -15
  180. package/dist/src/daemon/claude-worker.d.ts +2 -1
  181. package/dist/src/daemon/claude-worker.js +92 -45
  182. package/dist/src/daemon/codex-stream.js +16 -0
  183. package/dist/src/daemon/daemon.d.ts +8 -1
  184. package/dist/src/daemon/daemon.js +51 -14
  185. package/dist/src/daemon/delivery-router.js +6 -0
  186. package/dist/src/daemon/github-calls.d.ts +4 -0
  187. package/dist/src/daemon/github-calls.js +16 -7
  188. package/dist/src/daemon/github-ci.d.ts +6 -0
  189. package/dist/src/daemon/github-ci.js +170 -0
  190. package/dist/src/daemon/github-merge-call.d.ts +14 -0
  191. package/dist/src/daemon/github-merge-call.js +73 -0
  192. package/dist/src/daemon/github-merge.d.ts +3 -5
  193. package/dist/src/daemon/github-merge.js +52 -95
  194. package/dist/src/daemon/github-methods.d.ts +28 -0
  195. package/dist/src/daemon/github-methods.js +84 -16
  196. package/dist/src/daemon/github-operation.d.ts +2 -0
  197. package/dist/src/daemon/github-retry.d.ts +6 -0
  198. package/dist/src/daemon/github-retry.js +49 -0
  199. package/dist/src/daemon/github-ticket-status.d.ts +3 -0
  200. package/dist/src/daemon/github-ticket-status.js +294 -0
  201. package/dist/src/daemon/github-worker.js +2 -1
  202. package/dist/src/daemon/handoff-worker.d.ts +10 -0
  203. package/dist/src/daemon/handoff-worker.js +232 -0
  204. package/dist/src/daemon/issue-queue.d.ts +2 -0
  205. package/dist/src/daemon/issue-queue.js +186 -43
  206. package/dist/src/daemon/notify-journal.d.ts +18 -0
  207. package/dist/src/daemon/notify-journal.js +108 -0
  208. package/dist/src/daemon/notify-message.js +2 -1
  209. package/dist/src/daemon/notify.js +176 -37
  210. package/dist/src/daemon/operation-journal.d.ts +1 -0
  211. package/dist/src/daemon/operation-journal.js +4 -2
  212. package/dist/src/daemon/retrying-turn.d.ts +12 -0
  213. package/dist/src/daemon/retrying-turn.js +231 -0
  214. package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
  215. package/dist/src/daemon/ticket-dependency-waits.js +60 -0
  216. package/dist/src/daemon/ticket-registration.d.ts +8 -4
  217. package/dist/src/daemon/ticket-registration.js +76 -22
  218. package/dist/src/daemon/ticket-source.d.ts +4 -1
  219. package/dist/src/daemon/ticket-source.js +8 -2
  220. package/dist/src/decision-visibility.d.ts +2 -0
  221. package/dist/src/decision-visibility.js +10 -0
  222. package/dist/src/execution-pin.d.ts +4 -0
  223. package/dist/src/execution-pin.js +23 -2
  224. package/dist/src/host/start-delivery.d.ts +2 -1
  225. package/dist/src/host/start-delivery.js +1 -1
  226. package/dist/src/host/step-bridge-host.d.ts +1 -0
  227. package/dist/src/host/step-bridge-host.js +16 -0
  228. package/dist/src/host/world-reconciliation.d.ts +3 -0
  229. package/dist/src/host/world-reconciliation.js +9 -0
  230. package/dist/src/host/world-runs.js +14 -1
  231. package/dist/src/issue-identity.d.ts +6 -0
  232. package/dist/src/issue-identity.js +23 -0
  233. package/dist/src/service/queue-runs.d.ts +3 -0
  234. package/dist/src/service/queue-runs.js +39 -0
  235. package/dist/src/service/registry.d.ts +8 -2
  236. package/dist/src/service/registry.js +20 -8
  237. package/dist/src/service/replies.d.ts +2 -0
  238. package/dist/src/service/run-handlers.js +54 -30
  239. package/dist/src/service/service.d.ts +3 -0
  240. package/dist/src/service/service.js +3 -1
  241. package/dist/src/service/submission-handler.d.ts +1 -0
  242. package/dist/src/service/submission-handler.js +92 -13
  243. package/dist/src/service/worker-totals.d.ts +3 -0
  244. package/dist/src/service/worker-totals.js +52 -0
  245. package/dist/src/step-bridge.d.ts +9 -0
  246. package/dist/src/steps/handoff.d.ts +2 -0
  247. package/dist/src/steps/handoff.js +13 -0
  248. package/dist/src/ticket-dependencies.d.ts +1 -0
  249. package/dist/src/ticket-dependencies.js +15 -0
  250. package/dist/src/ticket-status.d.ts +8 -0
  251. package/dist/src/ticket-status.js +48 -0
  252. package/dist/src/token-prices.d.ts +21 -0
  253. package/dist/src/token-prices.js +108 -0
  254. package/dist/src/worker-usage.d.ts +31 -0
  255. package/dist/src/worker-usage.js +44 -0
  256. package/dist/src/workflow-admission.d.ts +1 -0
  257. package/dist/src/workflow-candidate.d.ts +29 -0
  258. package/dist/src/workflow-candidate.js +60 -0
  259. package/dist/src/workflow-contracts.d.ts +4 -0
  260. package/dist/src/workflow-contracts.js +32 -5
  261. package/dist/src/workflow-declaration.d.ts +12 -1
  262. package/dist/src/workflow-declaration.js +71 -13
  263. package/dist/src/workflow-discovery.d.ts +18 -0
  264. package/dist/src/workflow-discovery.js +33 -0
  265. package/dist/src/workflow-refusals.d.ts +6 -0
  266. package/dist/src/workflow-refusals.js +39 -0
  267. package/dist/src/workflows.d.ts +3 -1
  268. package/dist/src/workflows.js +2 -0
  269. package/dist/tools/admit-workflow.d.ts +13 -0
  270. package/dist/tools/admit-workflow.js +94 -0
  271. package/dist/tools/self-host-processes.js +7 -2
  272. package/dist/tools/self-host-project.d.ts +17 -0
  273. package/dist/tools/self-host-project.js +177 -0
  274. package/dist/tools/self-host-workflows.d.ts +1 -1
  275. package/dist/tools/self-host-workflows.js +2 -2
  276. package/dist/tools/self-host.js +30 -84
  277. package/dist/tools/self-update-host.d.ts +7 -0
  278. package/dist/tools/self-update-host.js +48 -1
  279. package/dist/tools/self-update.d.ts +1 -0
  280. package/dist/tools/self-update.js +85 -13
  281. package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
  282. package/dist/tools/workflow-bundle-dependencies.js +93 -0
  283. package/dist/tools/workflow-bundles.d.ts +20 -0
  284. package/dist/tools/workflow-bundles.js +173 -0
  285. package/dist/tools/workflow-candidate-counts.d.ts +8 -0
  286. package/dist/tools/workflow-candidate-counts.js +251 -0
  287. package/dist/tools/workflow-candidate-program.d.ts +11 -0
  288. package/dist/tools/workflow-candidate-program.js +78 -0
  289. package/dist/tools/workflow-candidate-source.d.ts +6 -0
  290. package/dist/tools/workflow-candidate-source.js +107 -0
  291. package/dist/tools/workflow-candidate-syntax.d.ts +15 -0
  292. package/dist/tools/workflow-candidate-syntax.js +219 -0
  293. package/dist/tools/workflow-folder-capture.d.ts +17 -0
  294. package/dist/tools/workflow-folder-capture.js +216 -0
  295. package/dist/tools/workflow-folders.d.ts +33 -0
  296. package/dist/tools/workflow-folders.js +150 -0
  297. package/dist/tools/workflow-source-effects.d.ts +10 -0
  298. package/dist/tools/workflow-source-effects.js +213 -0
  299. package/dist/workflows/analyze-chats.d.ts +15 -0
  300. package/dist/workflows/analyze-chats.declaration.json +22 -0
  301. package/dist/workflows/analyze-chats.js +250 -0
  302. package/dist/workflows/develop-prd.js +71 -3
  303. package/dist/workflows/develop-ticket.d.ts +1 -7
  304. package/dist/workflows/develop-ticket.js +47 -141
  305. package/dist/workflows/issue-labels.d.ts +3 -1
  306. package/dist/workflows/issue-labels.js +34 -0
  307. package/dist/workflows/ticket-prompts.d.ts +35 -0
  308. package/dist/workflows/ticket-prompts.js +143 -0
  309. package/package.json +25 -5
package/README.md CHANGED
@@ -23,6 +23,7 @@ What works now:
23
23
  - Cloving develops itself through `develop-issue`, with a Greptile review and an automatic merge.
24
24
  - The watch page shows the runs, the events and the work of the Claude worker as it occurs.
25
25
  - `cloving chats import -- FILE_A FILE_B` imports selected Claude Code transcripts into private runtime files without a service.
26
+ - `cloving chats redact IMPORT_ID` creates a redacted corpus and a separate counts manifest without a service.
26
27
 
27
28
  What does not work yet:
28
29
 
@@ -34,23 +35,145 @@ What does not work yet:
34
35
 
35
36
  ### Import selected chats
36
37
 
38
+ Chat import, redaction and analysis file operations currently require macOS. On Linux, the chat commands return `platform-unsupported` with exit status `1` before file access. The analysis file adapter also refuses with `platform-unsupported` before reading payloads.
39
+
37
40
  ```sh
38
41
  cloving chats import -- TRANSCRIPT_A.jsonl TRANSCRIPT_B.jsonl
42
+ cloving chats import --previous-import IMPORT_ID -- TRANSCRIPT_A.jsonl TRANSCRIPT_B.jsonl
43
+ cloving chats import --root PROJECTS_A --root PROJECTS_B --exclude-project project-skip -- TRANSCRIPT_A.jsonl
39
44
  ```
40
45
 
41
- Select each file explicitly. Relative paths use the current directory. The command does not scan directories or expand globs. Your shell can expand an unquoted glob. A source must be a regular file of at most 10 MiB (10,485,760 bytes). Symbolic links are refused. Sources keep their original bytes.
46
+ Select files positionally, projects folders with repeatable `--root`, or both. Relative paths use the current directory. At least one file or root is required. After `--`, every argument is a literal file path, including names that look like options. The command does not expand globs; your shell can expand an unquoted glob. A source must be a regular file of at most 10 MiB (10,485,760 bytes). Symbolic links are refused. Sources keep their original bytes.
47
+
48
+ Each root explicitly selects a projects folder. Claude Code normally stores projects below its configuration folder, whose default name is `.claude` below the home folder; `CLAUDE_CONFIG_DIR` can change that location. Cloving never discovers a root from configuration or reads settings to expand your selection. This layout does not guarantee supported transcript formats or complete history. [Claude Code session storage](https://code.claude.com/docs/en/sessions#where-transcripts-are-stored) and [subagent storage](https://code.claude.com/docs/en/sub-agents) describe the source layout.
49
+
50
+ `--exclude-project NAME` matches a direct project folder name exactly, preserving case. Repeat it for several names. Exclusions require roots and never affect positional files. Names cannot contain separators, parent-directory markers or glob patterns. An excluded subtree produces one `project-excluded` entry; its children are never listed or opened.
51
+
52
+ Positional files retain their order and repetitions and come before scanned files. Roots sort by canonical path, retaining repeated selections with stable ties. Entries sort by relative path using JavaScript string order. Scans select regular files ending in lowercase `.jsonl`; other filenames produce `unsupported-extension` without a content read. Root children have depth one. Session files at depth two and subagent files at depth four fit the limit. Folders at depth four are pending with `depth-limit` and disclose no descendant inventory.
53
+
54
+ Across all roots, at most 100 eligible scanned candidates receive a source-read attempt. A failed open, oversized file, invalid encoding or unsupported transcript still consumes a slot. Repeated roots and overlapping selections consume separate slots; positional files are outside this budget. All metadata candidates are ordered before slots are allocated. Later candidates are pending with `file-limit` and are never opened. Select a pending file explicitly in a later import. The supported macOS scanner refuses links in roots, ancestors and entries, canonical containment escapes, Git repository roots (including worktrees and bare repositories), and source or ancestor identity changes before reading bytes. Inaccessible directories produce unreadable subtree entries. Directory enumeration also refuses raced links; an unavailable kernel protection fails closed.
55
+
56
+ Each import accepts at most 50 MiB (52,428,800 bytes) of normalized corpus JSONL, measured as UTF-8 including newline separators. Repeated selections count toward this total. This bounds the message content retained for structure analysis. Exceeding the limit returns `output-failed` with exit code `1`, leaves no completed coverage manifest and may leave partial data files. Select fewer sources for each import; copy and conflict comparisons apply within each import.
42
57
 
43
58
  The supported Claude Code JSONL subset has `user` and `assistant` envelopes with a matching `message.role`. Content can be a string or an ordered array of `text`, `tool_use` and `tool_result` blocks. Records preserve the role, timestamp, session ID, tool fields and original block positions. Missing timestamps and session IDs become `null`. Thinking blocks and non-message metadata are omitted and counted. Unknown message shapes or blocks refuse the entire source. Claude Code's internal transcript format can change between versions.
44
59
 
45
- Each import creates `chats/IMPORT_ID/corpus.jsonl` and `coverage.json` below the resolved Cloving runtime directory. Selection uses a nonempty `CLOVING_RUNTIME_DIR`, then `XDG_RUNTIME_DIR/cloving`, then `XDG_STATE_HOME/cloving/run`, then `HOME/.local/state/cloving/run`. Relative settings resolve against the current directory. Destinations inside a Git repository are refused, including through symbolic links. New directories have mode `0700`; files have mode `0600`. These permissions add no encryption or redaction.
60
+ Each import creates `chats/IMPORT_ID/corpus.jsonl`, `structure.json` and `coverage.json` below the resolved Cloving runtime directory. Selection uses a nonempty `CLOVING_RUNTIME_DIR`, then `XDG_RUNTIME_DIR/cloving`, then `XDG_STATE_HOME/cloving/run`, then `HOME/.local/state/cloving/run`. Relative settings resolve against the current directory. Destinations inside a Git repository are refused, including through symbolic links. New directories have mode `0700`; files have mode `0600`. These permissions add no encryption or redaction.
61
+
62
+ New corpus records have `schemaVersion: 2` and a `sourceId` of `sha256:HEX:OFFSET`. The digest covers the exact source bytes. The offset is the zero-based byte position of the source line. `text` joins the message's text blocks with a newline; `blocks` retains each supported block's `position`. Tool result content is copied as JSON data. Positional-only imports retain coverage version 1 with its ordered `files` array. Imports containing roots write coverage version 2 with ordered `roots`, `exclusions`, fixed `limits` and ordered `entries`. Each entry identifies its zero-based positional or sorted-root selection, file or subtree kind, resolved path, status, fixed reason code, digest when available, record count and omission counts (`metadataRecords` and `thinkingBlocks`). Excluded and pending subtree entries count only themselves. Scanned oversized files are `unsupported` with `file-too-large`; positional oversized files retain `too-large`, including mixed imports. Refused sources contribute no records. Included sources also record `byteSize`, measured from the exact hashed bytes. Both coverage versions accept legacy entries without sizes. The final coverage manifest appears only after the corpus, structure and optional revision files close successfully. Output failure leaves no completed coverage manifest; partial data files may remain.
63
+
64
+ Use `--previous-import IMPORT_ID` before `--` to compare with one earlier import in the resolved runtime chats folder. It accepts one UUID v4 ID, using the same syntax as redaction. Missing, repeated and malformed values are usage errors (`missing-value`, `repeated-previous-import` and `invalid-import-id`). At least one current file or root is still required. The earlier coverage manifest must be valid and carry the same ID; completed partial imports are allowed. Existing revisions must also be valid with a positive safe revision number that can be incremented. Missing coverage, invalid metadata or linked inputs return `previous-import-refused`, exit `1`, a null import ID and zero counts before selection, source reads or new output.
65
+
66
+ Comparison writes private `revision.json` with `schemaVersion: 1`, `importId`, `previousImportId`, `evidenceRevision`, `counts` and ordered `entries`. An earlier import without a revision starts revision `1`; otherwise its revision increases by one. Each entry stores `path`, `root` (null for positional selections), `before` and `after` evidence, one `status` and one fixed `reason`. Absent evidence is null; present evidence contains a nullable digest and optional byte size. No message text is stored. Counts always include `new`, `missing`, `unchanged`, `changed`, `truncated` and `extended`, including zeros.
67
+
68
+ Selections match only by stored source path and selected root path, resolving each manifest's root indexes independently. Repeated keys pair in order. Current entries come first, followed by unmatched earlier entries in their earlier order. New selections are `new`; absent or currently refused selections are `missing`. Equal non-null digests are `unchanged`. An unavailable earlier digest or size makes differing evidence `changed`. Fewer bytes are `truncated`; more bytes are `extended` only when SHA-256 of exactly the earlier byte count equals the earlier digest. Other differences are `changed`. Prefixes use the same bounded source read; no source is reopened. Earlier entries never select sources or invent subtree descendants. Missing or truncated evidence forces `import-partial` and exit `1`. Revisions do not merge corpora, establish complete history or invoke later analysis. Without the option, no revision file is created and the summary retains its existing shape.
69
+
70
+ Version 2 preserves nonempty envelope `uuid` strings exactly, with `null` for unknown identity. It preserves boolean `isSidechain` flags, otherwise `null`. A nonempty `parentUuid` string is an observed link and explicit `null` is an observed root. Absent, empty or invalid parents are omitted and remain unknown. Import retains all supported branches and side chains and never uses API message IDs, paths, content or neighbors to infer identity.
71
+
72
+ The version 1 structure sidecar references each occurrence by zero-based `recordIndex` and `sourceId`, including repeated selections with the same source ID. It contains no message content or source paths. Known identity matches across the import mark copies of the earliest occurrence with equal role, text and blocks, ignoring object member order while preserving array order and exact JSON number tokens. All records stay in the corpus. Different content for one identity produces a conflict group containing every variant and its copies; no variant wins.
73
+
74
+ Structure groups nodes and observed links within known sessions. Null and empty session IDs remain separate, unlinked occurrences. Links with conflicting observed parents or unknown child identity are marked ambiguous. Forks require multiple distinct known child IDs through unambiguous links. Missing parent targets are counted once per session and target ID, separately from unknown parent fields. Cyclic groups include all observed links, including ambiguous links; their IDs form strongly connected groups in occurrence order. Counts include records, known sessions, forks, copies, conflict groups, unknown identities and missing parent targets, including zeros. `historyCompleteness` is always `"unknown"`: selected history and even an explicit root cannot establish complete history. Omitted metadata parents remain unresolved.
75
+
76
+ Console output contains counts, fixed codes and an opaque import ID. Source paths, exclusion names, event and session IDs, and transcript text stay in the private files. Root imports add `excluded` and `pending` entry totals. `refused` counts unreadable, unsupported and positional oversized entries. Exit code `0` and `import-complete` mean no refused or pending entries and, when comparing revisions, no missing or truncated evidence; exclusions alone permit success without comparison. Code `1` and `import-partial` mean refused, pending, missing or truncated entries when output completes. Output failures return code `1`, a null import ID and zeroed counts. Code `2` means invalid usage, before source or destination access. Importing performs only structural comparison; it makes no model or network call and does not execute transcript instructions. Full quality admission and release qualification remain pending.
77
+
78
+ ### Protected chat analysis files
79
+
80
+ The daemon provides `chats` capability methods `read-analysis` and `save-analysis` only to the registered `analyze-chats` workflow. A declaration must explicitly name `chats`; existing workflows retain their capabilities. Start one analysis with `cloving run analyze-chats --input input.json`, where the file contains exactly `{ "importId": "11111111-1111-4111-8111-111111111111" }` using your redacted import ID. The declaration allows eleven effect units: one capture, up to four restricted turns and four checkpoint saves, with two units reserved for question continuation. A run without a question charges one unit for capture and two per accepted batch. No evidence path is accepted.
81
+
82
+ `read-analysis` takes `{ importId }`, with the same lowercase UUID v4 syntax as redaction and the import ID as its protected target. It reads only `redaction.json`, `redacted.jsonl` and `structure.json` from the resolved runtime import directory. For one batch its completed result contains `{ snapshot }`, whose `importId`, `redaction`, `corpus` and `structure` fields are strings. Keep these strings in private durable state; parsing and serializing corpus numbers can lose their exact tokens. For multiple batches it returns the prepared batch snapshot, encoded context and captured-digest binding. Original corpus, coverage, revision and transcript content are never analysis inputs.
83
+
84
+ Each evidence file is bounded before retention. Capture streams up to 52,428,800 UTF-8 bytes of redacted corpus. Each metadata file, serialized batch document and save payload is bounded at 65,536 bytes. The complete model launch includes the fixed instruction, encoded batch evidence, projected structure and earlier accepted result. Overflow returns `analysis-too-large`; transport limits are unchanged. Missing redaction manifest or corpus returns `redaction-required`, malformed nonempty import IDs return `invalid-import-id`, and invalid or unsafe evidence returns `analysis-input-refused`. Manifest counts and identity fields must match the corpus. Sidecar validation retains original copy/conflict classes, using class markers rather than redacted content to check structure consistency.
85
+
86
+ `save-analysis` takes `{ importId, cycleId, reply }`, with the captured-digest binding in `batch` for multiple batches and `followUp` for a question continuation. It resolves evidence from the run's private capture instead of resending it with the reply, keeping saves within the payload bound. Checkpoint saves verify any supplied legacy `snapshot` against the captured batch. The reply is JSON text and validation uses the captured snapshot even if the files later change. Invalid JSON returns `analysis-invalid-json`; candidate refusal retains the validator's unique codes in declaration order. Accepted output writes and closes private `proposal-CYCLE_ID.txt` before creating `analysis-CYCLE_ID.json` as the completion marker. The JSON is the complete validator result, including every citation, counterevidence item and limitation. The deterministic plain-text proposal is at most 4,000 characters. Matching duplicate saves retain bytes and receipt identity; a matching proposal without a marker may finish. Conflicting or incomplete existing outputs remain unchanged and return `analysis-output-failed`. A proposal alone is not completed analysis. New files have mode `0600`; destinations and linked files use the import protections.
87
+
88
+ The pure batch preparer validates a captured snapshot, selects the longest consecutive whole-record prefix within 65,536-byte encoded launch and adapter documents, and refuses a fifth batch. It preserves record bytes, global occurrence indexes and captured copy/conflict classes, includes the earlier result as data, and measures the fixed instruction and prompt encoding shared with the restricted launcher. Raw metadata remains bounded at 65,536 bytes and the input corpus at 52,428,800 bytes. The workflow runs one restricted turn per batch and commits its checkpoint before preparing the next. Budget, batch, size, validation and worker stops leave the latest partial checkpoint available.
89
+
90
+ The internal pure `mergeAnalysisBatch({ previousResult, candidate, evidence, nextRecordIndex })` in `src/chats/analysis-merge.ts` merges one batch candidate into an earlier result, or starts with `previousResult: null`. Evidence contains the complete captured redacted records and their original structure; `nextRecordIndex` is the exclusive processed-prefix bound. References keep global indexes and source IDs. Unknown and future references are refused. Captured copy classes remain intact, and returned conflict groups retain only processed occurrences. Process ID, name and scope must match. New IDs append in first-seen order per collection; an existing ID must keep its collection and every non-reference field. Support and counterevidence references are deduplicated separately in record order. Disagreement returns only `analysis-merge-conflict`; neither input is changed. Callers remain responsible for retaining both variants privately.
91
+
92
+ The merge revalidates the cumulative process, including repetition, global ID uniqueness, links, non-gates and question citations. It accepts references and links to earlier processed evidence and items without requiring the new batch to stand alone. A valid `openQuestion` remains unresolved data in the returned result; a prior result with an unresolved question is refused as `analysis-input-refused`. The pure merge saves no checkpoint, establishes no coverage or completion, and grants no action authority.
93
+
94
+ Capture atomically publishes a private version 1 `checkpoint-0.json` and `proposal-0.txt` alongside its evidence. The initial checkpoint is partial, with a null result, zero covered records and the entire captured total uncovered. Each committed batch saves its own immutable `analysis-checkpoint-N.json` and `proposal-checkpoint-N.txt` inside that capture. Checkpoints bind the run, import, captured digest and zero-based batch index, and retain the cumulative accepted result, covered and uncovered counts, completed batch count and next record index. Proposals put status and counts before shortened inference text. Full coverage with an accepted result and no unresolved question is complete; other checkpoints remain partial.
95
+
96
+ The final batch save writes the complete checkpoint and final output without another effect. The one-batch path retains its three effect charges. Matching retries retain checkpoint bytes and receipts; conflicting batch writes fail with `analysis-output-failed`. Storage uses the pure merge and privately retains both variants of an `analysis-merge-conflict`, leaving the earlier checkpoint accepted and partial. A private conflict envelope binds digests and references to that accepted checkpoint and a separately retained incoming candidate, so each variant keeps its own file bound. Checkpoint files use the existing private file protections. These records grant no execution or publication authority. Completed turns and saves are reused after restart without repeated charges or usage. Interrupted or unknown turns start no replacement. One question may pause any batch before its checkpoint commits. Its answer permits one follow-up on the same captured batch and earlier accepted result; processing then continues with the remaining batches. A second question leaves the earlier accepted checkpoint partial and blocks the run. The CLI five-dollar cap applies to each turn, not the run.
97
+
98
+ Analysis reuse after an evidence revision is unavailable. `earlierAnalysisId` is not a supported analysis input, and `analysis:IMPORT_ID:CYCLE_ID` identifies a saved result without establishing checkpoint coverage. The batch pipeline and its checkpoints do not establish reuse across an evidence revision. Protected reuse capture, conclusion invalidation, coverage transfer and workflow integration remain unimplemented.
99
+
100
+ The internal pure `mapEvidenceReferences` helper in `src/chats/reference-mapping.ts` maps occurrences from two supplied coverage manifests and corpora plus revision entries. It validates each manifest's record counts against its corpus before assigning selection ranges, resolves root indexes separately and pairs repeated path/root selections in manifest order. Matches use source digests and canonical byte offsets within each pair, never UUIDs or redacted text. The returned pairs rewrite both `recordIndex` and `sourceId` in earlier record order, without modifying inputs. Invalid or inconsistent inputs return `analysis-reference-map-refused`.
101
+
102
+ Only unchanged selections and provably preserved records of extended selections map. An extension must retain the earlier occurrence offsets below the old byte count. A record maps only when a later current occurrence starts at or before that count, proving the record ended within the verified prefix. An unterminated final record, or a final record whose boundary cannot be proved from captured occurrences, stays unmapped. Refused and subtree entries contribute no records; changed, truncated, missing and new selections transfer none. The helper checks recorded revision metadata without rereading source bytes. It establishes neither checkpoint coverage nor authority and is not connected to `analyze-chats`.
103
+
104
+ A valid candidate with `openQuestion` writes only private `draft-CYCLE_ID.json`. This version 1 record binds the worker intent's run ID, cycle ID, operation ID and input digest, the captured snapshot's SHA-256 digest, the exact reply and its SHA-256 candidate digest. Batched drafts also bind the captured digest, batch index and exclusive next record index; their questions are validated against the captured processed prefix. Saving a draft does not commit that batch or advance coverage. Its `expiresAt` is fixed at 24 hours after the first draft save. The completed private result contains `{ kind: "analysis_draft", cycleId, candidateDigest, expiresAt, question }` for the durable question. Matching delivery, reconciliation and stop preserve the record's bytes, receipt and deadline, including after restart and deadline passage. Absent reconciliation and stop create nothing. Conflicting or incomplete drafts return `analysis-output-failed`. A draft cannot replace accepted output. A final save alongside a retained draft requires the same run, cycle, captured snapshot and draft candidate, plus an exact listed answer.
105
+
106
+ The first draft or final write creates a shared private `claim-CYCLE_ID.json` with the output kind and a digest of its bound input. A follow-up final save verifies that draft claim and creates a separate `final-claim-CYCLE_ID.json` bound to the draft and validated output. Exclusive creation admits only one output for that cycle, including concurrent conflicting saves. An incomplete or conflicting claim fails closed. Matching retries reuse the claim; older matching final outputs remain readable without one. This storage claim carries no execution authority.
107
+
108
+ The gate helper accepts optional `expiresAt` and forwards that exact deadline to the durable decision service. Replayed gate openings retain the existing deadline. The service preserves deadlines across restart, expires unanswered questions and refuses late answers without reopening settled gates. Opening a gate uses no protected-effect unit. Existing callers that omit the field retain their current behavior.
109
+
110
+ A saved private draft opens one durable question. Its private prompt contains the candidate digest and listed choices. The deadline remains 24 hours after the first draft save, including across restart. Use `cloving questions` to read the prompt. Answer with exactly one listed choice ID; case changes, surrounding whitespace and extra text are invalid. Operational watch views and automatic notifications use fixed text.
111
+
112
+ An exact choice runs one restricted follow-up turn with the same captured batch, earlier accepted result, original draft candidate and answer as encoded data. The reply passes the same cumulative merge and checkpoint rules before the next batch starts. The one-batch path retains round 2 follow-up scopes and five effect units. Batched follow-ups use round 5 scopes, separate from batch rounds 1–4. A batched run uses one capture plus two effect units for each accepted batch, with two more for a question draft save and its follow-up turn, within the existing limit of eleven. The question and its answer use no effect unit. There is no additional follow-up or automatic repair. The complete encoded follow-up must still fit the existing byte bounds; a size refusal keeps the accepted coverage partial.
113
+
114
+ The committed answer survives a host restart before continuation delivery. Recovery delivers one continuation, reuses the original read, turn and draft save, and preserves their scopes, payloads and charges. Changed evidence files do not replace the captured snapshot. A repeated answer command returns its original receipt without another continuation. The compiled Local World restart tests verify the one-batch path and a three-batch question path with four turns, nine charged units and each batch committed once, using synthetic data and fake workers. A restart between draft persistence and gate opening also keeps the original deadline.
115
+
116
+ Expiry blocks with `analysis-answer-missing`; an answer outside the exact listed IDs blocks with `analysis-answer-invalid`. Another question blocks with `analysis-question-limit`. Follow-up validation refusal blocks with `analysis-follow-up-refused`, and validator details stay private. Exhausted effect units block with `budget-exhausted`. Expiry, an invalid answer, follow-up refusal or a second question leaves the earlier accepted checkpoint and proposal partial, without committing the questioned batch. Draft persistence does not resolve a question or grant action authority.
117
+
118
+ Success returns `{ kind: "handoff_requested", cycleId, promotionId }`, where `promotionId` is an opaque analysis reference. It grants no execution or publication authority. The file adapter performs no git work, network request or model call. Redaction can leave private values, and validator acceptance checks shape and cited occurrences rather than factual accuracy. History completeness remains unknown and independent observations remain unestablished.
119
+
120
+ The analysis turn always uses the `claude` executable on the search path, including with `CLOVING_WORKER=codex`. Development workflows keep their configured worker. Analysis requires Claude Code major version 2, at least 2.1.302, with every required launch flag in its help text; an unavailable or unsupported CLI returns `analysis-worker-unavailable`. It accepts no `analyze-chats` profile or development-command override.
121
+
122
+ To configure subscription login:
123
+
124
+ 1. Run `claude setup-token` once and store the token in your secret manager.
125
+ 2. Set `CLOVING_ANALYSIS_TOKEN_COMMAND` before starting the daemon. Its value is a JSON array of one or more nonempty strings naming a program and its arguments, for example `CLOVING_ANALYSIS_TOKEN_COMMAND='["op","read","op://Development/Claude Analysis/token"]' cloving start`. The command must print only the stored token on stdout.
126
+ 3. Start an `analyze-chats` run. Immediately before each new turn, Cloving runs the command without a shell, with a 10-second deadline and a 4,096-byte limit shared by stdout and stderr. After removing one final line break, stdout must contain 1–512 printable ASCII characters without spaces or control characters. An unset or malformed setting, command failure, timeout, excessive output or invalid token blocks with `analysis-worker-unavailable` and a fixed cause category.
127
+
128
+ The fetched token goes only to the analysis process as `CLAUDE_CODE_OAUTH_TOKEN`. Cloving does not put it in its environment, development workers, logs or stored results, and does not fetch it for CLI probes or completed-turn replay. Analysis excludes `ANTHROPIC_API_KEY`, including a key present in the daemon environment. Cloving does not read the macOS keychain.
129
+
130
+ The fixed launch removes built-in tools with `--tools ""` and denies MCP tools separately, with an empty strict MCP configuration. Safe and restricted modes disable discovery; skills, hooks, project instructions, plugins, session persistence and Chrome integration are disabled. Each turn runs with a five-dollar cap in an empty private directory and uses a separate fixed system instruction and encoded evidence input. Only these restricted turns contact the model. See the [Claude CLI controls](https://code.claude.com/docs/en/cli-reference) and [subscription authentication](https://code.claude.com/docs/en/authentication).
131
+
132
+ Each CLI turn has its own five-dollar cap; effect units do not define a dollar budget for the run. Each complete escaped document, including any draft and answer, is limited to 65,536 bytes. Replies of at most 16,000 UTF-8 bytes are retained completely for validation. Larger replies return `analysis-reply-too-large`; unsuccessful turns return `analysis-turn-failed`. Both retain available usage. A completed private worker result is written before its report, so durable replay can reuse it. An interrupted or unknown completion never starts a replacement turn. Analysis creates no raw stdout/stderr turn logs or generic text summary. Worker records and accepted outputs remain private; operational views expose codes, counts, usage and opaque identities. Temporary CLI data is removed when the turn completes. Acceptance grants no execution, publication or workflow-generation authority.
133
+
134
+ ### Redact an imported corpus
135
+
136
+ ```sh
137
+ cloving chats redact IMPORT_ID
138
+ ```
139
+
140
+ Use the UUID v4 import ID printed by `chats import`. This command uses the same runtime directory selection and needs no service. It accepts version 1 and version 2 corpus records and coverage manifests. It validates coverage without reopening transcript paths. Original corpus, coverage and structure files remain unchanged.
141
+
142
+ The command writes `redacted.jsonl` and `redaction.json` beside the originals. New files have mode `0600`. Existing outputs are refused. The manifest records rule version `1`, record count, category counts (including zeros), and total replacements. It contains no matched values or replacement map. The manifest is written only after the redacted corpus closes successfully. A failure can leave an incomplete redacted corpus; there is no restart recovery.
143
+
144
+ `HOME` supplies the home folder. The rules replace home paths (including tilde forms), e-mail addresses, supported GitHub, Anthropic and OpenAI tokens, complete private-key blocks, and URLs with user information. `CLOVING_PRIVATE_TEXTS` adds literal texts with case ignored. For this command, an unset setting or `[]` adds no texts. Malformed lists, non-string entries and blank entries are refused. The issue-posting workflows still require a nonempty private-text list.
145
+
146
+ Redaction inspects message text, text blocks and nested tool input and result strings, including member names. Overlapping matches count once, with priority given to private keys, credential URLs, home paths, tokens, e-mail addresses, then private texts. Both stored copies of message text count. Source references, schema versions, identity fields, parent-field presence, record order and exact number tokens remain intact. The structure sidecar is private metadata outside redaction scope and is not a redacted export. Member-name collisions are refused.
147
+
148
+ Console output contains only fixed codes, counts and the import ID. Exit code `0` means success, `1` means processing failure, and `2` means invalid usage. Originals and metadata remain outside redaction scope. Both originals and outputs remain private runtime data. Redaction makes no model or network call and does not execute transcript instructions. It neither guarantees removal of every private value nor authorizes later model use.
149
+
150
+ ### Validate process analysis data
151
+
152
+ The pure `validateAnalysis(candidate, { records, structure })` function in `src/chats/analysis.ts` validates one version 1 process candidate against an ordered redacted corpus and its matching pre-redaction, in-memory `ImportStructure`. The evidence object keeps the API within the repository's two-parameter quality limit. Stored sidecar envelopes are not this input type. This pure validator makes no model call.
153
+
154
+ Candidates declare IDs and descriptions for inputs, ordered stages, branches, bounded loops, artifacts, stopping conditions, gates and non-gates. Branch links use `sourceStageId` and `targetStageId`; loops use ordered `stageIds` and `exhaustedStoppingConditionId`. Stages, gates and loops declare a claim `kind`, `sourceRefs` and `counterevidenceRefs`. References identify exact occurrences by `recordIndex` and `sourceId`. Copies add no support; observed repetition needs two directly cited original source IDs. Routine repair, accidental interruption and tool-imposed confirmation require matching non-gate classifications.
155
+
156
+ Version 1 also accepts an optional `openQuestion` object containing only `claimId`, `description`, `sourceRefs` and `choices`. It names a stage, gate or loop claim and cites distinct known occurrences from that claim's support and counterevidence, with no unrelated references. Two to four choices each contain only a unique nonempty `id` and an inert `description`. Malformed or multiple questions return `invalid-question`; absent claims or missing declared conflict return `question-without-conflict`. Missing and unknown sources retain their existing codes, as do recursive authority fields. Validation checks the declared relations and makes no claim that the prose describes a real conflict. A validated question remains unresolved analysis data.
46
157
 
47
- Corpus records have `schemaVersion: 1` and a `sourceId` of `sha256:HEX:OFFSET`. The digest covers the exact source bytes. The offset is the zero-based byte position of the source line. `text` joins the message's text blocks with a newline; `blocks` retains each supported block's `position`. Tool result content is copied as JSON data. The versioned coverage manifest has one entry per selection, including its resolved path, status, fixed reason code, digest when available, record count and omission counts (`metadataRecords` and `thinkingBlocks`). Refused sources contribute no records. The final manifest appears only after the corpus closes successfully.
158
+ Acceptance returns the candidate plus limitations preserving conflict groups, unknown history and unestablished independence. Refusal returns only fixed, unique codes in declaration order. Candidate fields are closed; authority and executable fields are refused, and descriptions remain inert. Validation does not certify redaction, evidence authenticity, inference quality, publication safety or permission for any action. Evidence content and exact numeric tokens remain unchanged. Full product admission remains pending CLV-002 through CLV-004.
48
159
 
49
- Console output contains counts, fixed codes and an opaque import ID. Source paths and transcript text stay in the private files. Exit code `0` means every source was included. Code `1` means partial coverage or an output failure. Code `2` means invalid usage. Importing does not analyze chats, call a model or execute transcript instructions. Full quality admission and release qualification remain pending.
160
+ The internal pure `inspectWorkflowGenerationText(analysis)` check in `src/chats/workflow-generation-text.ts` inspects the identifiers and prose of an `AnalysisResult`, including branch and loop links, condition descriptions and optional question choices. Each inspected field must contain 1–240 printable ASCII characters. It returns unique codes in this fixed order: `text-length`, `text-characters`, `machine-path`, `parent-traversal`, `email-form`, `user-mention`, `access-token-form`, `credential-form`. Known disclosure forms include absolute and home-relative paths, Windows paths and home placeholders, literal and percent-encoded parent traversal, addresses and mentions, common token prefixes, authorization values, credential assignments, private-key headers and URL user information. Pattern checks run only on fields within the length bound; text is never truncated, decoded or sanitized.
161
+
162
+ The text check takes no corpus input and never reads source references, counterevidence or limitations. It makes no file, process, network or model call and returns no inspected text or artifact. Empty codes establish only that these fixed checks found no violation; arbitrary prose can still contain private information. Callers must validate the analysis separately and retain provenance privately. This check grants no admission, execution or publication authority.
163
+
164
+ The internal pure `generateWorkflow(analysis, availability)` entry point in `src/chats/workflow-generation.ts` accepts one structurally validated `AnalysisResult` and a serializable snapshot `{ exports, methods }`. `exports` maps public module names to available named exports; `methods` maps capability names to available methods. No snapshot provider or installation command is supplied. The result is `generated` with one TypeScript source, candidate declaration and versioned JSON prompt assets plus a separate private report; `gap` with a private report and no artifact; or `refused` with fixed text-check codes only. Generation performs no I/O or model call. Availability, analysis and generation confer no authority; source admission and run authorization remain separate requirements.
165
+
166
+ The fixed `review-repair/v1` projection accepts exactly two executable stages in array order: an explicit-human-instruction or observed-repetition work stage, then a routine-repair stage with one of those claim kinds. One branch must link review to repair with the exact condition `Defect found.`. One explicit-human-instruction loop must list those stages in that order, use the exact exit condition `Checks and review pass.`, and reference an existing exhaustion stop. These phrases select this fixed protocol; arbitrary condition prose is never evaluated. The analysis iteration count is preserved, with two possible one-unit `claude` / `run-turn` effects per iteration and a declaration bound of 100 effects. Other work claims remain annotations; any extra executable stage, ambiguous control flow or unresolved analysis question returns a gap. Historical exceptions, rejected approaches, assistant claims, uncertainty, interruptions and tool confirmations cannot select executable work.
167
+
168
+ The projection permits at most one design question or configured action approval associated with the review stage, opened after a successful review. Gates on other stages return a gap. Factual decisions become required string inputs after the analysis inputs. Fields, functions, assets, stages and gates use reserved ordinal names. Submission fields resolve through the existing `{ cycleId, repository, input }` envelope without historical defaults. Worker completion requires an outer JSON result envelope whose `summary` is itself a JSON object with status `complete`, `repair` or `blocked`; malformed or unknown statuses block. Routine repair consumes the declared loop; exhaustion blocks. A gate returns `waiting_for_decision`. A host-authorized fresh SDK segment supplies the optional typed `{ gateId, decision, answer }` continuation. The generated code validates it before requesting effects and reconstructs the same bounded visits; existing durable operation receipts and run spending prevent accepted effects from repeating. After reconstructing the successful review, a design answer enters the remaining iterations as the `decision` payload slot. The worker must apply and check that answer before reporting completion, and subsequent repair receives it too. With no iterations remaining, the continuation blocks. An answer supplies no publication permission.
169
+
170
+ Generated declarations limit serialized submission fields to 16,384 UTF-8 bytes. Before requesting any effects, the workflow checks both stage payloads, including assets and the resolved workspace, against a 32,758-byte budget. This reserves 32,768 bytes for the JSON-encoded completed review envelope and 10 bytes for its property separator within the worker's 65,536-byte payload limit. The final summary JSON has a separate 16,000-byte limit, fitting within the worker's 16,000-character retention limit; oversized summaries block as `worker-blocked`. Repair receives the retained completed review without further truncation. An oversized review envelope blocks with `review-too-large` before a repair request; oversized run data blocks with `invalid-run-input` before spending. Both final payloads, including any design answer, are checked against 65,536 bytes before delivery. These limits include UTF-8 encoding and JSON escaping.
171
+
172
+ The report retains the process and stage summaries, ordinal name map, excluded annotations, non-gates, claim kinds, source references, counterevidence, conflicts and uncertainty limitations. **Retain it privately and exclude it from public source and assets.** The generator does not save it and never reads corpus records. Only checked descriptions enter assets, embedded identically as escaped JSON literals in the source. Synthetic tests exercise source checking, real SDK compilation, compiled-artifact admission, declaration loading, worker status handling, gates and continuation across process termination. Product admission remains subject to the separate accepted verification policy.
50
173
 
51
174
  ## Install
52
175
 
53
- The current pilot uses Node 26.9.0 and npm 11.19.1 on macOS 27.0 on arm64. The installation ranges in `package.json` do not qualify other workflow runtimes: workflow admission still requires exactly Node 26.9.0. GitHub-hosted macOS 15 arm64 qualification is pending its CI quality and packed-startup checks. Other platforms and runtime versions are unqualified.
176
+ Cloving supports macOS and Linux on arm64 and x64, with the Node and npm ranges in `package.json`. Workflow admission still requires exactly Node 26.9.0. CI runs the developer gate and packed-install checks on macOS 15 arm64 and Ubuntu 24.04 x64 with Node 26.9.0 and npm 11.19.1. Chat import and redaction currently require macOS; other platforms return `platform-unsupported` before file access. Windows is unsupported.
54
177
 
55
178
  A run needs these tools:
56
179
 
@@ -172,7 +295,11 @@ This first run uses `develop-prompt` on a test repository. It needs no ticket co
172
295
 
173
296
  The client commands find the service with no setting, because the two sides use the same default runtime directory (`~/.local/state/cloving/run`). `CLOVING_RUNTIME_DIR` selects a different place for the two sides. The directory above the runtime directory holds the authority state, the Local World data, the turn records and the git work trees.
174
297
 
175
- `cloving watch` serves a read-only page on `http://127.0.0.1:3663/` (`--port` selects a different port). The page shows the runs, the events, the work of the Claude worker as it occurs, and each pending decision with its approve and deny commands. `cloving start` starts the page too; `CLOVING_WATCH_PORT` selects its port, and the value `off` starts none.
298
+ `cloving watch` serves a local page on `http://127.0.0.1:3663/` (`--port` selects a different port). The page shows the runs, the events, the work of the Claude worker as it occurs, and each pending decision with its approve and deny commands. `cloving start` starts the page too; `CLOVING_WATCH_PORT` selects its port, and the value `off` starts none.
299
+
300
+ Each workflow board with supported inputs has **+ Add a card** at the bottom of its first column, including an empty board. It opens an inline form: **Issue number** for `develop-issue`, **Ticket ID** for `develop-ticket`, **Idea** for `develop-prd` and **Task** for `develop-prompt`. Declared workflows such as `analyze-chats` use the string fields in their bundled declaration. Unsupported inputs have no control. Select **Add card**, press Enter in a one-line field, or Command/Control+Enter in a text area to send. **Cancel** and Esc close the form. The service reply shows the admitted run ID or a refusal code; admitted cards arrive through the existing live stream. For `workflow-active`, add `cloving: ready` to the issue to queue it.
301
+
302
+ The form sends the same run request as the CLI and retains its random command ID for retries, including a lost reply. Retry the same form to avoid duplicate runs. The write endpoint accepts at most 16 KiB of JSON, requires the page's random token, matching Origin and a loopback/localhost Host with the bound port, and allows no cross-origin preflight. `CLOVING_WATCH_ACTIONS=off` removes both the form control and the endpoint; actions are enabled by default. The **Start a run** section remains available with prompts to copy into agents. Decisions and other run actions still use their displayed CLI commands.
176
303
 
177
304
  `cloving start` reads its settings from the environment. `cloving start --help` gives the form and the default of each setting:
178
305
 
@@ -180,15 +307,22 @@ The client commands find the service with no setting, because the two sides use
180
307
  - `CLOVING_TICKET_COMMAND`: the ticket command (see "Daily development").
181
308
  - `CLOVING_NOTIFY_COMMAND`: a JSON list naming a command to notify the operator about pending decisions, blocked runs and failed runs. It has no default.
182
309
  - `CLOVING_NEXT_COMMAND`: a JSON list of a command and its arguments that prints the next ready ticket ID for `--next`. It has no default.
310
+ - `CLOVING_ANALYSIS_TOKEN_COMMAND`: a JSON command array that prints the subscription token just before an analysis turn (see "Protected chat analysis files"). It has no default.
183
311
  - `CLOVING_CLAUDE_COMMAND`: a replacement of the `claude` command of the worker.
184
312
  - `CLOVING_PROFILES`: a JSON file of named Claude turn profiles, read once when the service starts.
313
+ - `CLOVING_WATCH_ACTIONS`: `off` disables starting runs from the board; default `on`.
185
314
  - `CLOVING_WATCH_PORT`: the port of the watch page, or `off`.
315
+ - `CLOVING_WATCH_REPOSITORY`: repository context for a separate watch process. `start` sets this from its service repository. The watch process uses its local origin and has no current-directory default.
186
316
  - `CLOVING_OWNERSHIP_LEASE_SECONDS` and `CLOVING_HOST_BOUND`: limits of the workflow host.
187
317
  - `CLOVING_GH_COMMAND`, `CLOVING_CHECK_COMMAND` and `CLOVING_PRIVATE_TEXTS`: settings of `develop-issue`; the GitHub command and private-text list also apply to `develop-prd`.
188
318
 
189
319
  A command setting is a JSON list of texts, for example `CLOVING_CHECK_COMMAND='["npm", "test"]'`.
190
320
 
191
- The notify command receives no added argument. Its standard input receives one JSON object with `event`, `runId`, `workflowId`, `subject` and `body`. The body contains the decision or outcome text already shown by `cloving questions` or `cloving status`, with complete response commands, up to 4000 characters. The daemon checks every 15 seconds and records each event before calling the command. The first check with no notification record only records existing events. Records survive restarts; a failed command is logged once and never retried. Commands have 30 seconds to finish. A stop after recording an event but before sending it can lose that notification, and decisions opened and closed between checks can be missed.
321
+ The notify command receives no added argument. Its standard input receives one JSON object with `event`, `runId`, `workflowId`, `subject` and `body`. The body contains the decision or outcome text already shown by `cloving questions` or `cloving status`, with complete response commands, up to 4000 characters. The daemon checks every 15 seconds. The first check with no notification record only records existing events.
322
+
323
+ The private `notify-events.json` journal records a notification as `pending` before calling the command and as `sent` after exit code 0. A failed command retries the same message after 1, 5, 15 and 60 minutes, with five attempts in total and a 30-second limit per attempt. The journal records attempt times and fixed failure reasons without command arguments or output. Pending and retryable failed entries resume after restart. Exhausted delivery logs one line naming its key. Old key-only records migrate as sent without sending them again.
324
+
325
+ Journal updates use a temporary file and rename. Sent and exhausted failed delivery details expire after seven days; their keys remain to prevent later duplicate delivery. Unfinished deliveries remain until resolved. A crash after command success but before recording that success can repeat delivery. Decisions opened and closed between checks can be missed.
192
326
 
193
327
  ## Daily development
194
328
 
@@ -197,7 +331,7 @@ The notify command receives no added argument. Its standard input receives one J
197
331
  - `develop-prompt` takes the text of a task. Use it for a small task with no ticket, or for a first try in a new repository.
198
332
  - `develop-ticket` takes a ticket ID and reads the ticket itself. Use it for the normal work on a ticket, when you want to read each diff and merge the branch yourself.
199
333
  - `develop-issue` takes a ticket ID too, and sends the work through a GitHub pull request. Use it for a small ticket with clear criteria, in a GitHub repository that Greptile reviews and that has a check command. It is the workflow of the continuous process.
200
- - `develop-prd` takes an idea, researches the code and web, reviews a draft PRD, and posts it as a GitHub issue. Set `CLOVING_PRIVATE_TEXTS` to a nonempty JSON list before starting the daemon. A refused post preserves the worktree and draft. A successful post removes the worktree and adds no label; read the issue and add `cloving: ready` when it is ready for implementation.
334
+ - `develop-prd` takes an idea, researches the code and web, reviews a draft PRD, and posts it as a GitHub issue. It then hands the issue number to `develop-issue` to build automatically. Set `CLOVING_PRIVATE_TEXTS` to a nonempty JSON list before starting the daemon. A refused post preserves the worktree and draft. If the handoff is refused, it adds `cloving: ready` so the issue queue can build it later. The PRD run retains `issue-N` as its promotion, shows the refusal code in its outcome reason, and removes its worktree after posting. If the fallback label fails, status also shows `issueUpdate: failed`; inspect the GitHub operation and queue the issue after resolving the failure.
201
335
 
202
336
  ```sh
203
337
  cloving start # terminal 1, in the repository
@@ -216,13 +350,15 @@ The rule: read the diff before you approve.
216
350
 
217
351
  Claude turns have web research tools by default, an empty GitHub CLI configuration folder, and no GitHub token environment variables. This is not a sandbox: a turn can still find other credentials. The PRD refusal rules catch the listed private text and common sensitive forms; unlisted private names can pass. A `post-uncertain` result requires checking GitHub before trying another run.
218
352
 
353
+ `cloving status RUN_ID --json` includes `run.input`, and `cloving status --json` includes `input` on each listed run. It is the normalized submission admitted with the run, such as `{"ticket":"297"}` for `develop-issue` or the submitted fields of a declared workflow. It survives service restarts and excludes resolver-added ticket text and repository paths. Older runs without a stored submission show `null`. The text status of one run shows `Input:` as compact JSON, limited to the first 200 characters with `…` appended when longer. Terminal control and hidden format characters are escaped.
354
+
219
355
  ### The ticket command
220
356
 
221
357
  `CLOVING_TICKET_COMMAND` (a JSON list of texts) names the ticket command. The contract of a ticket command:
222
358
 
223
359
  - It gets a ticket ID as its last argument and prints the text of the ticket.
224
360
  - For `develop-issue`, a first line that starts with `# ` gives the title of the pull request. With no such line, the title is `Cloving task <cycle ID>`.
225
- - For `develop-issue`, a line `GitHub issue: <owner>/<repository>#<number>` makes the pull request close that issue.
361
+ - For `develop-issue`, a line `GitHub issue: <owner>/<repository>#<number>` makes the pull request reference that issue. Cloving closes it after a merge only when the worker reports complete work.
226
362
 
227
363
  The daemon runs the ticket command when it admits a run. The printed text becomes the stored input of the run, so a later change of the ticket does not change the run. A ticket that the command does not give is refused with `ticket-not-available`. With no ticket command, `develop-ticket` is refused with `ticket-source-not-configured`. A ticket ID has letters, digits and the characters `. _ : -` only.
228
364
 
@@ -245,10 +381,22 @@ CLOVING_TICKET_COMMAND='["github-ticket"]' cloving start
245
381
 
246
382
  `cloving run develop-issue --next` and `cloving run develop-ticket --next` use the reserved ticket ID `next`, just like `--ticket next`. Set `CLOVING_NEXT_COMMAND` to a JSON list naming your selection command and its arguments. The daemon adds no argument. The command chooses a ready ticket in your tracker and prints its ID; the daemon trims it and reads the ticket with `CLOVING_TICKET_COMMAND`. Both commands share the resolver's 30-second deadline. Selection logic stays in your command, and each invocation submits only one run.
247
383
 
384
+ Set `CLOVING_QUEUE_SECONDS` to a whole number from 10 to 3600 to poll for ready issues. It requires both ticket commands. `CLOVING_QUEUE_LANES` sets the number of simultaneous `develop-issue` runs, from 1 to 4, and requires `CLOVING_QUEUE_SECONDS`; its default is 1. For example, `CLOVING_QUEUE_LANES=4` admits at most one new ticket each cycle until four lanes are busy. Each run has its own branch and work tree. Manual issue submissions share this limit and receive `workflow-active` when all lanes are busy. A ticket already being developed receives `ticket-active`.
385
+
386
+ Before enabling more than one lane, update the next-ticket command to honor `CLOVING_SKIP_TICKETS`. Cloving sets this environment variable to space-separated IDs of active tickets and tickets waiting for dependencies. The command must never print an ID from that list. It should print nothing when no eligible ticket remains. The service also checks selected tickets before admission.
387
+
388
+ In the ticket text returned by `CLOVING_TICKET_COMMAND`, each `#N` in a sentence starting with **Needs** inside `## Dependencies` is a hard dependency. For example, `Needs #123 and #124.` waits until both GitHub issues close. Other references and sections do not create dependencies. Cloving reads dependency state with `CLOVING_GH_COMMAND` (default `gh`); an unavailable or invalid response prevents admission. An open dependency returns `ticket-dependency-open` and puts the ticket in the skip list. Waiting tickets are checked again each cycle with a free lane, so a closed dependency makes its ticket eligible again.
389
+
390
+ `cloving status` and watch display busy lanes and skipped tickets with their reasons. A pending self-update holds new admissions while active lanes drain. Three unsuccessful runs in a row, counted in the order they finish, stop the queue; a successful handoff clears the streak. Ordinary restarts preserve it. After inspecting and reconciling the failures, start with `--reset-queue` to clear the streak. A conflict caused by an earlier lane's merge returns its diagnostics to a fix round. Lanes do not add CI runners or parallelize turns within one run.
391
+
248
392
  An empty selection gives `no-ready-ticket`; a failed command gives `next-ticket-not-available`. Missing configuration gives `next-source-not-configured`. Multiple IDs, an invalid ID, or `next` as the selected ID give `invalid-ticket-id`. Use only one of `--next`, `--ticket`, `--prompt` and `--input`.
249
393
 
250
394
  ### Review rounds of `develop-ticket` and `develop-prompt`
251
395
 
396
+ Worker turns retry an exact Codex capacity refusal or a Claude overload after 60 seconds, then 180 seconds, for at most three attempts. Each retry keeps the work tree, profile, budget and limits, and asks the worker to continue from the current work. The step keeps one operation identity and sums usage across attempts. Private turn journals and turn usage retain each attempt's model, token counts and cost estimate. Stopping during a wait returns `stopped`; a daemon restart fails the recorded operation for existing recovery. The same retry waits apply to the restricted analysis turn in `analyze-chats`. Other failures are not retried.
397
+
398
+ `CLOVING_FALLBACK_MODELS` accepts a JSON array of at most three nonempty model names, for example `'["gpt-6-astra"]'`. Without the setting or with `[]`, every attempt keeps the main model. The first attempt and first retry use the main model; each later attempt uses the next configured fallback within the existing attempt limit. With three attempts, only the first fallback can be used. The worker command must explicitly name a model with `-m NAME`, `--model NAME` or `--model=NAME`; otherwise it stays unchanged and the private journal records `fallback-model-unavailable`. Fallbacks apply only after capacity refusals and never change the fixed analysis model of `analyze-chats`. A fallback model can have a higher price. Each attempt's estimate uses its own model's price; an unknown price or missing token count leaves the total estimate unknown. Malformed settings refuse daemon start with `invalid-fallback-models`.
399
+
252
400
  A run of `develop-ticket` or `develop-prompt` has one to three review rounds. Each round does one Claude Code turn in the work tree of the run, on the branch `cloving/<ticket id>-…`, and then opens one approval. The first round makes the work tree. The approval text has the round, the ticket ID, the branch, the commits, the changed files and the report of the worker. Read the diff before you decide. The decision has these results:
253
401
 
254
402
  - An approval completes the run. It does not merge the branch. Merge the branch yourself.
@@ -257,6 +405,19 @@ A run of `develop-ticket` or `develop-prompt` has one to three review rounds. Ea
257
405
 
258
406
  The worker is not confined. It runs the `claude` command of the search path as the current user, with file edits and shell commands permitted in the work tree and a spend limit of 5 USD for each turn. `CLOVING_CLAUDE_COMMAND` (a JSON list of texts) replaces the command.
259
407
 
408
+ Single-run status (`cloving status RUN_ID`, also with `--json`) and watch show each worker command call and its run totals. Token counts retain the reported input, cache read and output categories separately. Codex input includes cached tokens; cache read tokens are not added to input tokens. These selected counts are not complete billing usage or a record of all subagent work. Missing or invalid fields show `Token usage not known`. Costs show the worker-reported USD price when available, otherwise `≈ 7.82 USD (estimate)` when an estimate is available, otherwise `Price not known`; a reported zero shows `0.00 USD`. Totals for an unfinished run are labeled `so far`. JSON uses `null` for unknown numeric values, retains the total reported price field `claudeCostUsd`, and adds `costDisplay` to the run and each worker turn.
409
+
410
+ The built-in table uses standard short-context prices read on **2026-10-18** from the [OpenAI pricing page](https://openai.com/api/pricing/), in USD per million tokens:
411
+
412
+ | Model | Input | Cached input | Output |
413
+ | ------------- | ----: | -----------: | -----: |
414
+ | `gpt-6.1-sol` | 2.00 | 0.10 | 10.00 |
415
+ | `gpt-6-astra` | 10.00 | 1.00 | 50.00 |
416
+
417
+ `CLOVING_TOKEN_PRICES` accepts a JSON object mapping model names to non-negative finite `input`, `cachedInput` and `output` prices per million tokens. Its entries override matching built-in entries and retain the others. Estimates use the reported model or the worker command's model argument, with `estimatedCostUsd` totals only when every ended turn has an estimate. There is no live price lookup; estimates do not change budgets or limits.
418
+
419
+ `CLOVING_WORKER` selects `claude` (the default) or `codex`. With `codex`, `CLOVING_CODEX_COMMAND` replaces the native Codex command and must still emit native Codex events. A shared-format wrapper uses `CLOVING_CLAUDE_COMMAND` with the `claude` worker selection. Its last JSONL result supplies `total_cost_usd` and `usage.input_tokens`, `usage.cache_read_input_tokens` and `usage.output_tokens`, including on errors. Report only the usage and price of that command call; the wrapper must normalize resumed sessions or multi-input accounting. Cloving does not infer the output format from the executable or model name or reconstruct missing usage from old logs.
420
+
260
421
  Set `CLOVING_PROFILES` to a file such as:
261
422
 
262
423
  ```json
@@ -299,11 +460,35 @@ step. The bundled development workflows keep their existing targets.
299
460
 
300
461
  ### Pull requests of `develop-issue`
301
462
 
302
- A run of `develop-issue` opens no approval. The GitHub worker makes the work tree from the newest commit of the default branch of `origin`. After the Claude turn, the worker pushes the branch and opens a pull request. The title and the closed issue come from the ticket text (see "The ticket command"). Then the worker waits for the Greptile review of the pushed commit, and asks for a review with a comment after 5 minutes.
463
+ A run of `develop-issue` opens no approval. The GitHub worker makes the work tree from the newest commit of the default branch of `origin`. After the Claude turn, the worker pushes the branch and opens a pull request. The title and the referenced issue come from the ticket text (see "The ticket command"). The pull request body uses `Refs owner/repository#N`, so the merge itself does not close the issue. Then the worker waits for the Greptile review of the pushed commit, and asks for a review with a comment after 5 minutes.
464
+
465
+ When the ticket names a GitHub issue, the workflow adds `cloving: in progress` after preparing the work tree and removes `cloving: ready` and `cloving: blocked`. A blocked run adds `cloving: blocked` and removes `cloving: in progress`. Start and blocked label failures leave the run's outcome unchanged. Cloving creates the labels it adds or removes with the fixed color `ededed`; existing labels have their color and description reset by `gh label create --force`.
466
+
467
+ The last worker report must end with exactly one line: `Ticket status: complete` or `Ticket status: partial`. A partial report must put `Remaining:` immediately before a list with one `- ` item per line, followed by the status line. The worker records the parsed status and remaining items before shortening the summary, so summary limits cannot remove those fields. If the preserved fields exceed the result byte limit, the turn fails with `claude-result-too-large` and retains bounded cost metadata; the full report stays in the private turn log. Older recorded turns still use summary parsing. Missing, repeated, unknown or malformed status reports count as partial with the remaining item "The worker report has no ticket status line."
468
+
469
+ After a merge, a protected GitHub operation updates the issue body and labels. Complete work closes the issue with reason `completed` and adds "Completed in pull request #M." to `## Cloving status`. Partial work keeps the issue open and appends `- Pull request #M: partial. Remaining: ...` to that section at the end of the body, preserving earlier partial lines and other body text. Items are separated by semicolons. The issue privacy check runs before new remaining text is written; a refusal uses "Remaining work is in the private run report." instead. No issue comment is posted. Status writes add and remove only workflow labels, preserving unrelated label edits. Before a body write, Cloving rereads the issue and refuses a changed body with `github-issue-conflict`; the merged outcome records `issueUpdate: failed`. GitHub does not support conditional PATCH requests for this endpoint, so this check cannot make the read and write atomic.
470
+
471
+ Partial work removes `cloving: in progress` and adds `cloving: ready`, so the next queue cycle can select the same issue and read its remaining work from the issue body. The third partial run adds `cloving: blocked` instead of `cloving: ready`. Completion removes these workflow labels. Status operations reconcile the pull request number in the stored history after a restart to avoid duplicate writes. The effect limit stays at 20 units: the post-merge status operation replaces the final label operation.
472
+
473
+ Partial history comes only from a hidden HTML comment at the end of the issue body, beginning with `cloving-status`. Its JSON object has `version: 1`, a `partialRuns` array of pull request numbers and remaining-item lists, and an optional `completedPullRequest`. Double hyphens and greater-than signs in JSON strings are escaped so they cannot end the comment. Each write regenerates the readable section from this data and replaces the exact previously generated section. Other Markdown stays unchanged, including status examples, lists, tables, HTML and unclosed fences. Missing, broken or unknown metadata counts as zero earlier partial runs and is replaced on the next write; readable text is never used to reconstruct history. A block followed by other issue text is left unchanged and counts as missing terminal metadata.
303
474
 
304
- When the ticket names a GitHub issue, the workflow adds `cloving: in progress` after preparing the work tree and removes `cloving: ready` and `cloving: blocked`. A blocked run adds `cloving: blocked` and removes `cloving: in progress`; a completed merge removes `cloving: in progress`. Operators add `cloving: ready`. Label failures leave the run's outcome unchanged. Cloving creates the labels it adds or removes with the fixed color `ededed`; existing labels have their color and description reset by `gh label create --force`.
475
+ Partial history, including its JSON block, is bounded by the 48,000-byte ticket-read limit, reserving 2,048 bytes for the title, issue reference and worker notes. If the new remaining items do not fit, Cloving uses the private-report notice above. If even the notice cannot fit alongside the existing body and history, the issue update fails without requeueing it. Retries check the same size limit before restoring queue labels. Review and merge-fix turns receive the previous report's remaining items, including preserved items from shortened summaries, and must reassess each item before reporting completion.
305
476
 
306
- At the confidence score 5/5, the worker merges the pull request with a merge commit, with no human approval. Then it removes the remote branch, the work tree and the local branch. With a lower score, the Claude worker gets the summary and the findings of the review for one more round. After three such rounds, or after 60 minutes with no review, the run stops and the pull request stays open.
477
+ Merged runs retain `handoff_requested` with `ticketStatus: complete` or `partial`, visible in `cloving status` and watch. If the issue update fails, `issueUpdate: failed` is also shown. The queue reports each terminal run without a successful handoff and continues, stopping after three such runs in a row. A successful handoff resets the count; the queue journal preserves the count and run IDs across daemon restarts. Inspect failed GitHub operations and reconcile their issues before requeueing them. A later partial run does not mark an earlier blocked ticket as done.
478
+
479
+ To resume a queue stopped at this limit, stop the service with Control-C, inspect the failures and reconcile the affected issues, then run `cloving start --reset-queue` with the same repository, runtime and queue settings. This clears the failure streak once and preserves tracked commands. Ordinary restarts, including automatic self-updates, preserve the count. Resetting the queue does not change issue labels or retry blocked issues.
480
+
481
+ At the confidence score 5/5, the worker runs the local merge check and waits for CI of the exact head it will merge, including an updated head pushed after merging the default branch. When CI passes, it merges the pull request with a merge commit, with no human approval. Then it removes the remote branch, the work tree and the local branch. With a lower score, the Claude worker gets the summary and the findings of the review for one more round. After three such rounds, or after 60 minutes with no review, the run stops and the pull request stays open.
482
+
483
+ Set `CLOVING_REQUIRED_CHECKS` to a JSON list of check run names, such as `["quality", "quality_linux"]`, to require those checks. Without the setting, all GitHub Actions checks on the head commit are required. The newest run of each name decides; `success`, `neutral` and `skipped` pass. With no setting and no Actions checks after a 60-second grace period, a repository without CI can merge. A configured missing check keeps waiting. CI is polled every 30 seconds with a separate 90-minute limit. A timeout returns `ci-pending` and blocks without a fix round.
484
+
485
+ A failed check returns `ci-failed` with failed check names, failed steps and failed log tails, with private texts hidden and at most 6,000 characters. It starts a worker fix round that must repair CI, including failures the pull request did not cause, and commit the fix. The new commit receives a new Greptile review and CI gate. Review, local check, conflict and CI repairs share the existing limit of three fix rounds. Cloving does not retry failed CI jobs.
486
+
487
+ The watch board keeps blocked `develop-issue` runs in `Stopped`. A validated local merge report shows `PR merged · #N`. A later completed run for the same established GitHub issue must have its own validated merge report to show `Ticket completed by later run`. The original and later run have separate links. Either local proof prevents a GitHub read. These marks preserve the recorded run outcome.
488
+
489
+ Otherwise, the board reads the recorded pull request with `CLOVING_GH_COMMAND` (default `gh`). It validates the PR and issue identities against the configured repository's local origin. The first queued or active read shows `Checking PR`. A confirmed merge shows `PR merged · #N`; a failed read shows `PR status unavailable`. An unmerged PR shows `Needs attention`. A separate `Stale` mark appears when the last successful unmerged observation is older than 120 seconds, including after a failed refresh. A successful refresh resets its age. Confirmed merges never become stale.
490
+
491
+ Reads run in the background, one at a time, with a five-second limit. Board loads can refresh unresolved PRs at least 60 seconds after the previous attempt started. Confirmed merges remain cached until the watch process stops. A later normal page load shows each result.
307
492
 
308
493
  The GitHub worker does not push a change that has the path of the home folder in a file or in a commit message. `CLOVING_PRIVATE_TEXTS` (a JSON list of texts) adds more texts to refuse, with no difference between capital and small letters. `CLOVING_CHECK_COMMAND` (a JSON list of texts) names a check that must pass in the work tree before a merge. `CLOVING_GH_COMMAND` replaces the `gh` command. The worker uses the `gh` login and the git credentials of the current user.
309
494
 
@@ -316,7 +501,7 @@ In a continuous process, Cloving develops one ticket after the next with `develo
316
501
  3. Follow the run on the watch page, or with `cloving status RUN_ID` and `cloving events RUN_ID --follow`.
317
502
  4. Check the merge. The run completes when the GitHub worker merges the pull request. The watch page shows the number of the pull request of the run (`PR_NUMBER`). It is also `pullRequest.number` of the run in `http://127.0.0.1:3663/data.json`. The pull request number and the issue number are different.
318
503
  - Make sure that the pull request is merged: `gh pr view PR_NUMBER --json state,mergedAt`.
319
- - Make sure that the issue is closed: `gh issue view ISSUE_NUMBER --json state`.
504
+ - Check the ticket status and issue: `gh issue view ISSUE_NUMBER --json state,body,labels`. Complete work closes the issue; partial work stays open and requeues until the third partial run blocks it.
320
505
  5. Update the local `main`: `git switch main`, then `git pull --ff-only`.
321
506
  6. When the repository is the Cloving checkout itself, also restart the service, so that it runs the new code. Stop it with Control-C, then start it again with the same settings.
322
507
  7. Go to the next issue.
@@ -339,7 +524,7 @@ Copy a prompt, replace the words in capital letters, and give it to a coding age
339
524
  ```text
340
525
  Set up Cloving for my repository REPOSITORY_PATH. Ask me for each value that you do not know. Do not push, publish or merge anything in this setup.
341
526
 
342
- 1. Check the platform: macOS on arm64, with the Node and npm versions of "engines" in package.json of Cloving. Check that git and the claude command work. If I want develop-issue, check that gh has a login and that Greptile reviews the GitHub repository.
527
+ 1. Check the platform: macOS or Linux on arm64 or x64, with the Node and npm versions of "engines" in package.json of Cloving. Check that git and the claude command work. If I want develop-issue, check that gh has a login and that Greptile reviews the GitHub repository.
343
528
  2. If I have no Cloving checkout, clone https://github.com/cloving-ai/cloving-ai.git to ~/cloving. In the checkout, run: npm ci --ignore-scripts
344
529
  3. Write a launcher ~/.local/bin/cloving that runs src/cli.ts of the checkout with the correct node program, and make it executable. Do not use npm link, and do not install the npm package with the name cloving. Make sure that ~/.local/bin is on the search path and that "cloving --version" works.
345
530
  4. Ask me which tracker has my tickets. Write a ticket command for that tracker in ~/.local/bin. The command gets a ticket ID as its last argument and prints the text of the ticket. For develop-issue, the first line is "# " and the title, and one line is "GitHub issue: OWNER/REPOSITORY#NUMBER". Test the command with one ticket ID, and show me the output.
@@ -362,7 +547,7 @@ For each issue:
362
547
  3. Follow the run with "cloving status RUN_ID" and "cloving events RUN_ID --follow".
363
548
  4. When the run completes, get the number of its pull request (PR_NUMBER). It is pullRequest.number of the run in http://127.0.0.1:3663/data.json, and the watch page shows it too. Then:
364
549
  - Make sure that the pull request is merged: gh pr view PR_NUMBER --json state,mergedAt
365
- - Make sure that the issue is closed: gh issue view ISSUE_NUMBER --json state
550
+ - Check the issue state, remaining work and queue labels: gh issue view ISSUE_NUMBER --json state,body,labels
366
551
  5. Update the local main branch: git switch main, then git pull --ff-only
367
552
  6. If the repository is the Cloving checkout itself, restart the service in its terminal session, so that it runs the new code. Stop it with Control-C, then start it again with the same settings.
368
553
  7. Run the checks of the repository on the new main branch.
@@ -387,7 +572,7 @@ Help me write a ticket for Cloving. A Claude Code worker gets the full text of t
387
572
  # TITLE
388
573
  One line. For develop-issue, it becomes the title of the pull request.
389
574
  GitHub issue: OWNER/REPOSITORY#NUMBER
390
- Only for develop-issue. The pull request closes this issue.
575
+ Only for develop-issue. The pull request references this issue; complete work closes it after the merge.
391
576
  ## Problem
392
577
  What is wrong now, with facts.
393
578
  ## Desired outcome
@@ -435,10 +620,12 @@ A workflow is a TypeScript file in the form of `workflows/develop-ticket.ts`:
435
620
 
436
621
  - An exported async function with the directive `"use workflow"` as its first statement. The directive needs the compiler of the Workflow SDK, not only TypeScript.
437
622
  - The first argument is the input of the run. A resolver of the daemon makes it, for example `cycleId`, `ticketId`, `ticket` and `repository`. After a decision, the host calls the function again with the decision (`gateId`, `decision` and `answer`) as the second argument. The function must examine the decision before it asks for an effect, as `decided()` in `workflows/develop-ticket.ts` does. If it does not, it does the work of round 1 again.
438
- - The step helpers come from the subpaths of `cloving/workflows`: `protectedEffect` from `cloving/workflows/steps/protected-effect`, `decisionGate` from `cloving/workflows/steps/decision-gate` and `loadCycle` from `cloving/workflows/steps/load-cycle`. The type `CycleOutcome` comes from `cloving/workflows`. An import of `cloving/workflows` starts no service.
623
+ - The step helpers come from the subpaths of `cloving/workflows`: `protectedEffect` from `cloving/workflows/steps/protected-effect`, `decisionGate` from `cloving/workflows/steps/decision-gate` and `loadCycle` from `cloving/workflows/steps/load-cycle` and `handoff` from `cloving/workflows/steps/handoff`. The type `CycleOutcome` comes from `cloving/workflows`. An import of `cloving/workflows` starts no service.
439
624
  - `protectedEffect` asks the daemon for one effect of a capability, for example a Claude turn. `decisionGate` opens a decision for a person.
440
625
  - The function returns a `CycleOutcome`: `waiting_for_decision`, `handoff_requested` or `blocked`.
441
626
 
627
+ For `handoff_requested`, the daemon retains the workflow's `promotionId` in the completion event. An optional bounded `reason` records a handoff refusal while preserving the promotion. `cloving status <runId>` shows the promotion as `Promotion:` and the reason beside the outcome; JSON status includes `run.outcome.promotionId` and `run.outcome.reason`. Watch retains the promotion in the run model and completed-run details. This identifier is an opaque reference and grants no authority.
628
+
442
629
  ```ts
443
630
  import type { CycleOutcome } from "cloving/workflows";
444
631
  import { decisionGate } from "cloving/workflows/steps/decision-gate";
@@ -518,7 +705,12 @@ To register another workflow, put `<name>.declaration.json` beside `<name>.ts`:
518
705
  "maximumBytes": 65536,
519
706
  "fields": {
520
707
  "prompt": { "type": "string" },
521
- "rounds": { "type": "integer", "minimum": 1, "maximum": 10, "optional": true }
708
+ "rounds": {
709
+ "type": "integer",
710
+ "minimum": 1,
711
+ "maximum": 10,
712
+ "optional": true
713
+ }
522
714
  }
523
715
  }
524
716
  }
@@ -526,8 +718,37 @@ To register another workflow, put `<name>.declaration.json` beside `<name>.ts`:
526
718
 
527
719
  The ID accepts 1–64 letters, digits or hyphens. The effect limit is 1–100; capabilities must contain `claude`, `github`, or both. The input byte limit is 1–1048576, subject to the service's own limit. Fields accept nonempty strings, integers with optional inclusive bounds, or booleans. Fields are required unless `optional` is `true`; unknown fields are rejected. Bounds apply only to integers.
528
720
 
721
+ Optional `handoffs` lists distinct registered workflow IDs. Registration refuses unknown targets. The execution pin retains these permissions with each run: target order does not affect the pin, while adding or removing a target changes it. Omitted and empty lists keep existing pins. Stored permissions survive restart and retained workflow versions. The built-in `develop-prd` declares `develop-issue` as its only handoff target and calls it after creating an issue; the other built-in workflows declare none. A refused PRD handoff adds `cloving: ready` for the queue and records the refusal in the PRD run's outcome.
722
+
723
+ Use `handoff` inside a workflow to submit input to a declared target:
724
+
725
+ ```typescript
726
+ import { handoff } from "cloving/workflows/steps/handoff";
727
+
728
+ const runId = await handoff({
729
+ workflowId: "develop-issue",
730
+ input: { ticket: "CLV-123" },
731
+ });
732
+ ```
733
+
734
+ The step returns the target run ID after admission through the operator submission path. It spends one protected-effect unit. The caller’s pinned `handoffs` list must include the target; otherwise the step fails with `handoff-not-declared`. The target validates and resolves the input through its normal submission contract. Refusal fails with `handoff-input-refused` and admits no target run. If the target returns `workflow-active`, the worker makes at most ten attempts, one second apart, then fails with `handoff-start-timeout`. The worker retains the active-target refusal count across restarts. Replays reuse command ID `handoff:<operation ID>` and return the same run ID; separate step calls have distinct operation IDs. After a lost submission reply, recovery replays that same command ID only while the source run, lease, budget and workflow pin still permit dispatch. Status checks and stop never start a target run.
735
+
736
+ Hand-offs record `parentRunId` and `chainId` (the first run's ID) in the new run's admission. A chain permits at most five runs, including the first; attempting a sixth fails with `chain-limit` and starts no run. This fixed limit follows ancestry, so separate children of the same parent do not consume each other's chain length. The records and limit survive service restarts. `cloving status` shows the parent, chain and child run IDs in text and JSON; watch cards link to parent and child runs across workflows.
737
+
738
+ Optional `stages` contains 1–12 entries in board order. Each entry has a unique `name` (1–64 ASCII letters, digits or hyphens), a unique `label` (1–40 printable characters on one line, without outer spaces), and `operations`, `gateKinds`, or both. At least one selector array must be nonempty. Operations match the `scope.operation` passed to `protectedEffect`, using 1–128 ASCII letters, digits or `._:/-`; gate kinds are `approval` and `question`. Selectors cannot repeat within an array or belong to two stages of the same selector type. Unknown fields and labels `Other`, `Done` and `Stopped` are rejected.
739
+
740
+ For declared stages, the board uses the latest committed `effect.requested` or `gate.opened` sequence, preserving that event's start time. Unmatched events and runs without a relevant event use Other. Decisions and worker output do not advance a card; later requests can move it back. Declared labels are followed by Other, Done and Stopped. Existing terminal retention rules take precedence. Unstaged workflows and `develop-issue` keep their existing columns and placement. Declarations load at service startup; stages provide display metadata and grant no capabilities.
741
+
742
+ The bundled `analyze-chats` stages are Read input (`read-analysis`), Analyze (`turn-analysis`) and Save result (`save-analysis`). The Analyze selector names its operation; the model method remains `run-turn:analyze-chats`.
743
+
529
744
  After startup, `cloving status` lists the declared ID. Submit JSON with `cloving run review-run --input input.json`. The function receives `{ cycleId, repository, input }`, where `cycleId` is `task-` followed by 12 characters derived from the command ID, `repository` is the service repository, and `input` retains the checked JSON values. Invalid input is rejected before a run starts. Invalid declarations or duplicate workflow IDs or function names stop startup.
530
745
 
746
+ Startup retains each complete compiled project under `workflow-bundles` in private daemon state, beside the generated `project` directory. Each content-addressed snapshot includes workflow sources, declarations, registration manifests, all compiler artifacts, and the installed dependency graph and Node identity. Snapshots bind to the canonical repository and survive later source edits or removal. Storage lookup validates every captured file and returns `pinned-workflow-unavailable` for missing or changed bytes; startup refuses reuse of a matching damaged snapshot without repairing it. No retained generation is deleted automatically. Routing pinned runs through these stored generations remains pending.
747
+
748
+ Candidate policy checking is available internally through `checkWorkflowCandidate` in `src/workflow-candidate.ts`. It takes serializable analysis facts, an export snapshot and a candidate declaration containing `workflow` and `methods`. Each declared capability requires a nonempty list of distinct method names. The existing workflow sidecar format is unchanged. Results are sorted, unique refusal codes; an empty list grants no admission or execution authority.
749
+
750
+ The caller supplies imported symbol names, literal effect pairs (`null` represents a dynamic value), direct-effect and unsupported-form findings, and known call and unit totals. Unknown portions of a total still require runtime budget enforcement. This pure checker does not analyze source or verify these facts. Source-to-facts analysis, SDK candidate compilation and the installed candidate admission adapter remain unimplemented.
751
+
531
752
  ## Development of Cloving
532
753
 
533
754
  ```sh
@@ -538,7 +759,7 @@ node dist/src/cli.js --help
538
759
  npm pack --ignore-scripts
539
760
  ```
540
761
 
541
- `npm pack` prints the file name of the tarball. Install it into a separate project with `npm install --ignore-scripts --omit=dev /absolute/path/to/TARBALL`, then run `./node_modules/.bin/cloving --version` or `./node_modules/.bin/cloving start`. The package includes the emitted CLI, start program, compiler and bundled workflows; startup uses the installed runtime dependencies. The package has no installation hooks. An import of `cloving/workflows` gives the step helpers `loadCycle`, `protectedEffect` and `decisionGate`, and starts no service.
762
+ `npm pack` prints the file name of the tarball. Install it into a separate project with `npm install --ignore-scripts --omit=dev /absolute/path/to/TARBALL`, then run `./node_modules/.bin/cloving --version` or `./node_modules/.bin/cloving start`. The package includes the emitted CLI, start program, compiler and bundled workflows; startup uses the installed runtime dependencies. The package has no installation hooks. An import of `cloving/workflows` gives the step helpers `loadCycle`, `protectedEffect`, `decisionGate` and `handoff`, and starts no service.
542
763
 
543
764
  `npm run check` and `npm run test:concurrency` currently reject admission because the complete quality policy is not implemented. Setup checks are not product acceptance. State and retained releases will live outside the package installation; installing or updating the package does not activate a daemon or migrate state.
544
765
 
@@ -546,7 +767,17 @@ Run `npm run test:scale` on an otherwise idle machine to measure authority openi
546
767
 
547
768
  `npm run check:quality` checks the npm manifest, lock and installed versions, regenerates TypeScript output in memory, inventories first-party files, and runs strict compilation, the proposed module catalog, workflow/effect boundaries, suppression and test-integrity checks, formatting, typed lint, structural checks, clone detection and the regular local test suite. Production modules require at least 90% line, statement and function coverage and 85% branch coverage per file. Coverage uses native V8 ranges mapped to the syntax tree of the exact Node-stripped source; unimported production files fail. Class and named object types allow at most 24 callable members; interfaces allow 10. Clone checks support ordinary TypeScript and JavaScript; JSX is rejected until separately qualified. `npm run check:lint`, `npm run check:format` and `npm run test:quality` run individual development checks.
548
769
 
549
- Set `CLOVING_CHECK_TIME_FACTOR` to a whole number from 1 to 5 (default 1) to multiply only the developer gate test and coverage command time limits.
770
+ The quality report includes elapsed time and the effective limit in milliseconds for each bounded command: npm inventory, clone detection, tests and coverage conversion. Commands using more than 80 percent of their limit produce a warning in the JSON report and on stderr after the report. Failed checks print the collected timings and warnings with the original error and a failing exit status. Warnings preserve a passing result. If your change made the check slower, address the warning in the same ticket.
771
+
772
+ The watch scrolling tests launch an isolated headless Chrome process at desktop and phone widths. They require Chrome on the test machine. Run them with `node --test test/watch-scrolling.test.ts`. The fixture serves bundled Preact and board data over loopback and blocks external page resources.
773
+
774
+ Before a ticket report, commit your work and run `npm run check:clean`. It refuses uncommitted changes, archives the committed tree of `HEAD` into a temporary directory, copies the worktree's `node_modules`, and runs `node tools/check-quality.ts` there with the same environment and exit code. Ignored build output such as `dist/` is absent, so tests that rely on stale local files fail. The temporary copy is removed after success, failure or SIGINT/SIGTERM interruption.
775
+
776
+ Set `CLOVING_CHECK_TIME_FACTOR` to a whole number from 1 to 5 (default 1) to multiply the developer gate test and coverage command time limits, the inner hook check limit (5 seconds by default), and the installed hook limit (10 seconds by default). With factor 3, the hook limits are 15 and 30 seconds, respectively.
777
+
778
+ Set `CLOVING_TEST_CONCURRENCY` to a whole number from 1 to 8 to override the developer gate's test worker count. Without the setting, the gate uses one fewer worker than the available cores, with a minimum of 2 and a maximum of 4. macOS CI sets it to 3 and Ubuntu CI sets it to 4. Pull requests from branches of this repository run both jobs on local runners with the label `cloving-ci` (macOS arm64 and Linux arm64) with 8 workers; the local macOS runner accepts the macOS version of its machine. Invalid values fail with an error.
779
+
780
+ Tests that stop a child process should await `stopChild` from `test/stop-child.ts`. Use `stopChildGroup` for a detached process group, including descendants whose leader has exited. Both helpers send `SIGTERM`, wait up to five seconds times `CLOVING_CHECK_TIME_FACTOR` for exit, then escalate to `SIGKILL`. When a test requires a hard crash or an unresponsive child, start that child with `NODE_V8_COVERAGE` omitted so a kill cannot interrupt its coverage recording. Keep coverage enabled for ordinary child runs and recovery runs.
550
781
 
551
782
  The catalog and checker configuration are local proposals. The bounded SDK artifact, dependency-surface, production-coverage and mandatory-test inventory components have local qualification fixtures. Protected activation of their records, verifier execution and hooks remain pending. Dependency snapshots and supplied review records cannot activate a candidate. The local quality command cannot accept a product candidate. Static workflow checks are not isolation or replay proof. Deterministic sequencing fixtures qualify the test mechanism; they do not prove ownership or storage concurrency in the service.
552
783