cloving 1.0.1 → 1.1.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 (177) hide show
  1. package/README.md +145 -14
  2. package/dist/src/authority/commands.js +10 -2
  3. package/dist/src/authority/reconciliation.d.ts +2 -0
  4. package/dist/src/authority/reconciliation.js +10 -1
  5. package/dist/src/authority/transitions.d.ts +1 -1
  6. package/dist/src/authority/transitions.js +2 -0
  7. package/dist/src/broker/actor/authority-session.d.ts +2 -0
  8. package/dist/src/broker/actor/authority-session.js +3 -4
  9. package/dist/src/chats/analysis-batches.d.ts +24 -0
  10. package/dist/src/chats/analysis-batches.js +83 -0
  11. package/dist/src/chats/analysis-contract.d.ts +80 -0
  12. package/dist/src/chats/analysis-contract.js +16 -0
  13. package/dist/src/chats/analysis-data.d.ts +3 -0
  14. package/dist/src/chats/analysis-data.js +85 -0
  15. package/dist/src/chats/analysis-input.d.ts +12 -0
  16. package/dist/src/chats/analysis-input.js +83 -0
  17. package/dist/src/chats/analysis-links.d.ts +2 -0
  18. package/dist/src/chats/analysis-links.js +25 -0
  19. package/dist/src/chats/analysis-prompt.d.ts +10 -0
  20. package/dist/src/chats/analysis-prompt.js +16 -0
  21. package/dist/src/chats/analysis-proposal.d.ts +2 -0
  22. package/dist/src/chats/analysis-proposal.js +32 -0
  23. package/dist/src/chats/analysis-provenance.d.ts +4 -0
  24. package/dist/src/chats/analysis-provenance.js +99 -0
  25. package/dist/src/chats/analysis-shape.d.ts +3 -0
  26. package/dist/src/chats/analysis-shape.js +228 -0
  27. package/dist/src/chats/analysis.d.ts +3 -0
  28. package/dist/src/chats/analysis.js +42 -0
  29. package/dist/src/chats/corpus-input.d.ts +5 -0
  30. package/dist/src/chats/corpus-input.js +288 -0
  31. package/dist/src/chats/destination.js +3 -1
  32. package/dist/src/chats/import.d.ts +8 -6
  33. package/dist/src/chats/import.js +195 -23
  34. package/dist/src/chats/input-file.d.ts +3 -0
  35. package/dist/src/chats/input-file.js +32 -0
  36. package/dist/src/chats/normalize.d.ts +11 -2
  37. package/dist/src/chats/normalize.js +18 -2
  38. package/dist/src/chats/previous-import.d.ts +5 -0
  39. package/dist/src/chats/previous-import.js +34 -0
  40. package/dist/src/chats/reader.d.ts +7 -1
  41. package/dist/src/chats/reader.js +46 -15
  42. package/dist/src/chats/redact-import.d.ts +14 -0
  43. package/dist/src/chats/redact-import.js +141 -0
  44. package/dist/src/chats/redaction.d.ts +41 -0
  45. package/dist/src/chats/redaction.js +161 -0
  46. package/dist/src/chats/revision-input.d.ts +3 -0
  47. package/dist/src/chats/revision-input.js +180 -0
  48. package/dist/src/chats/revision.d.ts +48 -0
  49. package/dist/src/chats/revision.js +94 -0
  50. package/dist/src/chats/selection.d.ts +25 -0
  51. package/dist/src/chats/selection.js +109 -0
  52. package/dist/src/chats/source.d.ts +23 -0
  53. package/dist/src/chats/source.js +135 -0
  54. package/dist/src/chats/structure.d.ts +57 -0
  55. package/dist/src/chats/structure.js +216 -0
  56. package/dist/src/cli/arguments.d.ts +8 -0
  57. package/dist/src/cli/arguments.js +94 -13
  58. package/dist/src/cli/client.d.ts +1 -0
  59. package/dist/src/cli/client.js +38 -1
  60. package/dist/src/cli/commands.d.ts +3 -0
  61. package/dist/src/cli/commands.js +29 -11
  62. package/dist/src/cli/main.d.ts +2 -1
  63. package/dist/src/cli/main.js +61 -14
  64. package/dist/src/cli/render.d.ts +2 -0
  65. package/dist/src/cli/render.js +70 -2
  66. package/dist/src/cli/usage-render.d.ts +3 -0
  67. package/dist/src/cli/usage-render.js +28 -0
  68. package/dist/src/cli/usage.d.ts +3 -2
  69. package/dist/src/cli/usage.js +38 -6
  70. package/dist/src/cli/watch-actions.d.ts +10 -0
  71. package/dist/src/cli/watch-actions.js +58 -0
  72. package/dist/src/cli/watch-board-view.d.ts +32 -0
  73. package/dist/src/cli/watch-board-view.js +88 -15
  74. package/dist/src/cli/watch-board.js +6 -1
  75. package/dist/src/cli/watch-card-client.d.ts +1 -0
  76. package/dist/src/cli/watch-card-client.js +44 -0
  77. package/dist/src/cli/watch-card-forms.d.ts +2 -0
  78. package/dist/src/cli/watch-card-forms.js +33 -0
  79. package/dist/src/cli/watch-card-reply.d.ts +5 -0
  80. package/dist/src/cli/watch-card-reply.js +34 -0
  81. package/dist/src/cli/watch-card-view.d.ts +40 -0
  82. package/dist/src/cli/watch-card-view.js +86 -0
  83. package/dist/src/cli/watch-client.d.ts +1 -0
  84. package/dist/src/cli/watch-client.js +118 -0
  85. package/dist/src/cli/watch-model.js +31 -14
  86. package/dist/src/cli/watch-page.d.ts +1 -1
  87. package/dist/src/cli/watch-page.js +71 -85
  88. package/dist/src/cli/watch-pr-board.d.ts +3 -0
  89. package/dist/src/cli/watch-pr-board.js +26 -0
  90. package/dist/src/cli/watch-pr-command.d.ts +15 -0
  91. package/dist/src/cli/watch-pr-command.js +37 -0
  92. package/dist/src/cli/watch-pr-identity.d.ts +20 -0
  93. package/dist/src/cli/watch-pr-identity.js +62 -0
  94. package/dist/src/cli/watch-pr-observer.d.ts +19 -0
  95. package/dist/src/cli/watch-pr-observer.js +108 -0
  96. package/dist/src/cli/watch-pr-settings.d.ts +8 -0
  97. package/dist/src/cli/watch-pr-settings.js +47 -0
  98. package/dist/src/cli/watch-reads.js +4 -0
  99. package/dist/src/cli/watch-render.d.ts +16 -2
  100. package/dist/src/cli/watch-render.js +10 -3
  101. package/dist/src/cli/watch-run-input.d.ts +14 -0
  102. package/dist/src/cli/watch-run-input.js +49 -0
  103. package/dist/src/cli/watch-server.d.ts +3 -0
  104. package/dist/src/cli/watch-server.js +44 -6
  105. package/dist/src/cli/watch-start-view.d.ts +18 -0
  106. package/dist/src/cli/watch-start-view.js +77 -0
  107. package/dist/src/cli/watch-turn.d.ts +1 -0
  108. package/dist/src/cli/watch-turn.js +7 -2
  109. package/dist/src/cli/watch-usage.d.ts +6 -0
  110. package/dist/src/cli/watch-usage.js +65 -0
  111. package/dist/src/cli/watch-view.d.ts +15 -0
  112. package/dist/src/cli/watch-view.js +61 -17
  113. package/dist/src/cli.js +48 -6
  114. package/dist/src/daemon/analysis-launch.d.ts +5 -0
  115. package/dist/src/daemon/analysis-launch.js +81 -0
  116. package/dist/src/daemon/analysis-token.d.ts +6 -0
  117. package/dist/src/daemon/analysis-token.js +88 -0
  118. package/dist/src/daemon/analysis-worker.d.ts +22 -0
  119. package/dist/src/daemon/analysis-worker.js +219 -0
  120. package/dist/src/daemon/chats-files.d.ts +15 -0
  121. package/dist/src/daemon/chats-files.js +113 -0
  122. package/dist/src/daemon/chats-worker.d.ts +12 -0
  123. package/dist/src/daemon/chats-worker.js +200 -0
  124. package/dist/src/daemon/claude-turn.d.ts +10 -0
  125. package/dist/src/daemon/claude-turn.js +96 -15
  126. package/dist/src/daemon/claude-worker.d.ts +4 -1
  127. package/dist/src/daemon/claude-worker.js +99 -47
  128. package/dist/src/daemon/codex-stream.js +16 -0
  129. package/dist/src/daemon/daemon.d.ts +3 -0
  130. package/dist/src/daemon/daemon.js +16 -7
  131. package/dist/src/daemon/github-methods.js +4 -0
  132. package/dist/src/daemon/github-ticket-status.d.ts +3 -0
  133. package/dist/src/daemon/github-ticket-status.js +268 -0
  134. package/dist/src/daemon/issue-queue.d.ts +1 -0
  135. package/dist/src/daemon/issue-queue.js +115 -24
  136. package/dist/src/daemon/operation-journal.d.ts +1 -0
  137. package/dist/src/daemon/operation-journal.js +4 -2
  138. package/dist/src/daemon/retrying-turn.d.ts +9 -0
  139. package/dist/src/daemon/retrying-turn.js +185 -0
  140. package/dist/src/host/world-reconciliation.d.ts +2 -0
  141. package/dist/src/host/world-reconciliation.js +6 -0
  142. package/dist/src/host/world-runs.js +9 -1
  143. package/dist/src/service/run-handlers.js +15 -23
  144. package/dist/src/service/worker-totals.d.ts +3 -0
  145. package/dist/src/service/worker-totals.js +28 -0
  146. package/dist/src/ticket-status.d.ts +8 -0
  147. package/dist/src/ticket-status.js +45 -0
  148. package/dist/src/token-prices.d.ts +19 -0
  149. package/dist/src/token-prices.js +76 -0
  150. package/dist/src/worker-usage.d.ts +22 -0
  151. package/dist/src/worker-usage.js +44 -0
  152. package/dist/src/workflow-candidate.d.ts +29 -0
  153. package/dist/src/workflow-candidate.js +59 -0
  154. package/dist/src/workflow-contracts.d.ts +2 -0
  155. package/dist/src/workflow-contracts.js +27 -5
  156. package/dist/src/workflow-declaration.d.ts +1 -1
  157. package/dist/src/workflow-declaration.js +3 -1
  158. package/dist/src/workflows.d.ts +1 -0
  159. package/dist/src/workflows.js +1 -0
  160. package/dist/tools/self-host-processes.js +7 -2
  161. package/dist/tools/self-host-workflows.d.ts +1 -1
  162. package/dist/tools/self-host-workflows.js +2 -2
  163. package/dist/tools/self-host.js +6 -6
  164. package/dist/tools/self-update-host.d.ts +7 -0
  165. package/dist/tools/self-update-host.js +48 -1
  166. package/dist/tools/self-update.d.ts +1 -0
  167. package/dist/tools/self-update.js +85 -13
  168. package/dist/workflows/analyze-chats.d.ts +10 -0
  169. package/dist/workflows/analyze-chats.declaration.json +9 -0
  170. package/dist/workflows/analyze-chats.js +73 -0
  171. package/dist/workflows/develop-ticket.d.ts +1 -7
  172. package/dist/workflows/develop-ticket.js +42 -141
  173. package/dist/workflows/issue-labels.d.ts +3 -1
  174. package/dist/workflows/issue-labels.js +34 -0
  175. package/dist/workflows/ticket-prompts.d.ts +35 -0
  176. package/dist/workflows/ticket-prompts.js +133 -0
  177. package/package.json +5 -3
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,107 @@ 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 declared workflow charges three effect units for sequential read, one model turn and save. No evidence path is accepted.
46
81
 
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.
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. 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. Original corpus, coverage, revision and transcript content are never analysis inputs.
48
83
 
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.
84
+ Each evidence file is bounded before retention. The redacted corpus, serialized snapshot, complete escaped model document (`{ prompt: analysisModelInput(snapshot) }`) and final serialized save payload must each fit 65,536 UTF-8 bytes. The model-input formatter includes instructions and the full sidecar. 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, snapshot, reply }`. 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. Workflow integration, streaming capture, cumulative merge, immutable coverage checkpoints, batch save bounds and restart across batches remain pending; the running workflow retains its single-snapshot limits above.
89
+
90
+ 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. Import revisions classify source changes but do not map analysis citations or transfer analyzed record ranges. Reuse requires an implemented and verified batch pipeline with result merging, committed complete or partial coverage checkpoints, replay binding and durable question/answer follow-up. The pure batch preparer and saved validator results do not satisfy that prerequisite.
91
+
92
+ A candidate with `openQuestion` currently returns `analysis-input-refused` from `save-analysis`, reconciliation and stop without creating output. The pure question validator is implemented; private drafts, durable analysis questions, expiry and answer follow-up are pending. The workflow still uses one turn and three effect units.
93
+
94
+ 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.
95
+
96
+ 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.
97
+
98
+ To configure subscription login:
99
+
100
+ 1. Run `claude setup-token` once and store the token in your secret manager.
101
+ 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.
102
+ 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.
103
+
104
+ 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.
105
+
106
+ 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. The command runs once with a five-dollar budget in an empty private directory and uses a separate fixed system instruction and encoded evidence input. Only this turn contacts 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).
107
+
108
+ 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.
109
+
110
+ ### Redact an imported corpus
111
+
112
+ ```sh
113
+ cloving chats redact IMPORT_ID
114
+ ```
115
+
116
+ 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.
117
+
118
+ 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.
119
+
120
+ `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.
121
+
122
+ 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.
123
+
124
+ 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.
125
+
126
+ ### Validate process analysis data
127
+
128
+ 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.
129
+
130
+ 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.
131
+
132
+ 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.
133
+
134
+ 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.
50
135
 
51
136
  ## Install
52
137
 
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.
138
+ 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
139
 
55
140
  A run needs these tools:
56
141
 
@@ -172,7 +257,11 @@ This first run uses `develop-prompt` on a test repository. It needs no ticket co
172
257
 
173
258
  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
259
 
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.
260
+ `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.
261
+
262
+ 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.
263
+
264
+ 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
265
 
177
266
  `cloving start` reads its settings from the environment. `cloving start --help` gives the form and the default of each setting:
178
267
 
@@ -180,9 +269,12 @@ The client commands find the service with no setting, because the two sides use
180
269
  - `CLOVING_TICKET_COMMAND`: the ticket command (see "Daily development").
181
270
  - `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
271
  - `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.
272
+ - `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
273
  - `CLOVING_CLAUDE_COMMAND`: a replacement of the `claude` command of the worker.
184
274
  - `CLOVING_PROFILES`: a JSON file of named Claude turn profiles, read once when the service starts.
275
+ - `CLOVING_WATCH_ACTIONS`: `off` disables starting runs from the board; default `on`.
185
276
  - `CLOVING_WATCH_PORT`: the port of the watch page, or `off`.
277
+ - `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
278
  - `CLOVING_OWNERSHIP_LEASE_SECONDS` and `CLOVING_HOST_BOUND`: limits of the workflow host.
187
279
  - `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
280
 
@@ -222,7 +314,7 @@ Claude turns have web research tools by default, an empty GitHub CLI configurati
222
314
 
223
315
  - It gets a ticket ID as its last argument and prints the text of the ticket.
224
316
  - 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.
317
+ - 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
318
 
227
319
  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
320
 
@@ -249,6 +341,8 @@ An empty selection gives `no-ready-ticket`; a failed command gives `next-ticket-
249
341
 
250
342
  ### Review rounds of `develop-ticket` and `develop-prompt`
251
343
 
344
+ 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, model, 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 retain each attempt. Stopping during a wait returns `stopped`; a daemon restart fails the recorded operation for existing recovery. The same retry rule applies to the restricted analysis turn in `analyze-chats`. Other failures are not retried.
345
+
252
346
  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
347
 
254
348
  - An approval completes the run. It does not merge the branch. Merge the branch yourself.
@@ -257,6 +351,19 @@ A run of `develop-ticket` or `develop-prompt` has one to three review rounds. Ea
257
351
 
258
352
  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
353
 
354
+ 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.
355
+
356
+ 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:
357
+
358
+ | Model | Input | Cached input | Output |
359
+ | --- | ---: | ---: | ---: |
360
+ | `gpt-6.1-sol` | 2.00 | 0.10 | 10.00 |
361
+ | `gpt-6-astra` | 10.00 | 1.00 | 50.00 |
362
+
363
+ `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.
364
+
365
+ `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.
366
+
260
367
  Set `CLOVING_PROFILES` to a file such as:
261
368
 
262
369
  ```json
@@ -299,12 +406,30 @@ step. The bundled development workflows keep their existing targets.
299
406
 
300
407
  ### Pull requests of `develop-issue`
301
408
 
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.
409
+ 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.
410
+
411
+ 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`.
412
+
413
+ 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."
414
+
415
+ 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.
416
+
417
+ 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.
418
+
419
+ 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
420
 
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`.
421
+ 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.
422
+
423
+ 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.
424
+
425
+ 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.
305
426
 
306
427
  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.
307
428
 
429
+ The watch board keeps blocked `develop-issue` runs in `Stopped`. A qualifying later local run shows `Done in a later run`. Otherwise, the board reads the recorded pull request with `CLOVING_GH_COMMAND` (default `gh`). The service supplies its repository, and the board checks the local origin and the recorded PR identity. A confirmed merge shows `PR merged #N`; a read failure shows `PR status unavailable`. Other PR states keep `Needs attention`. These marks do not change the run outcome.
430
+
431
+ 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.
432
+
308
433
  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
434
 
310
435
  ## Continuous process
@@ -316,7 +441,7 @@ In a continuous process, Cloving develops one ticket after the next with `develo
316
441
  3. Follow the run on the watch page, or with `cloving status RUN_ID` and `cloving events RUN_ID --follow`.
317
442
  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
443
  - 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`.
444
+ - 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
445
  5. Update the local `main`: `git switch main`, then `git pull --ff-only`.
321
446
  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
447
  7. Go to the next issue.
@@ -339,7 +464,7 @@ Copy a prompt, replace the words in capital letters, and give it to a coding age
339
464
  ```text
340
465
  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
466
 
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.
467
+ 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
468
  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
469
  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
470
  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 +487,7 @@ For each issue:
362
487
  3. Follow the run with "cloving status RUN_ID" and "cloving events RUN_ID --follow".
363
488
  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
489
  - 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
490
+ - Check the issue state, remaining work and queue labels: gh issue view ISSUE_NUMBER --json state,body,labels
366
491
  5. Update the local main branch: git switch main, then git pull --ff-only
367
492
  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
493
  7. Run the checks of the repository on the new main branch.
@@ -387,7 +512,7 @@ Help me write a ticket for Cloving. A Claude Code worker gets the full text of t
387
512
  # TITLE
388
513
  One line. For develop-issue, it becomes the title of the pull request.
389
514
  GitHub issue: OWNER/REPOSITORY#NUMBER
390
- Only for develop-issue. The pull request closes this issue.
515
+ Only for develop-issue. The pull request references this issue; complete work closes it after the merge.
391
516
  ## Problem
392
517
  What is wrong now, with facts.
393
518
  ## Desired outcome
@@ -528,6 +653,10 @@ The ID accepts 1–64 letters, digits or hyphens. The effect limit is 1–100; c
528
653
 
529
654
  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
655
 
656
+ 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.
657
+
658
+ 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.
659
+
531
660
  ## Development of Cloving
532
661
 
533
662
  ```sh
@@ -546,7 +675,9 @@ Run `npm run test:scale` on an otherwise idle machine to measure authority openi
546
675
 
547
676
  `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
677
 
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.
678
+ 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.
679
+
680
+ 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.
550
681
 
551
682
  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
683
 
@@ -1,3 +1,4 @@
1
+ import { isTicketStatus } from "../ticket-status.js";
1
2
  import { isInstant } from "./instants.js";
2
3
  import { isBlockedReason } from "../workflow-contracts.js";
3
4
  import { numberFields, textFields, } from "./transitions.js";
@@ -65,12 +66,19 @@ const shapes = {
65
66
  "resume-run": revised,
66
67
  "complete-run": {
67
68
  required: ["expectedRevision"],
68
- optional: ["outcome", "outcomeReason"],
69
+ optional: ["outcome", "outcomeReason", "ticketStatus", "issueUpdate"],
69
70
  validators: {
70
71
  outcome: (value) => value === "handoff_requested" || value === "blocked",
71
72
  outcomeReason: isBlockedReason,
73
+ ticketStatus: isTicketStatus,
74
+ issueUpdate: (value) => value === "failed",
72
75
  },
73
- consistent: (fields) => fields["outcomeReason"] === undefined || fields["outcome"] === "blocked",
76
+ consistent: (fields) => (fields["outcomeReason"] === undefined ||
77
+ fields["outcome"] === "blocked") &&
78
+ (fields["ticketStatus"] === undefined ||
79
+ fields["outcome"] === "handoff_requested") &&
80
+ (fields["issueUpdate"] === undefined ||
81
+ fields["outcome"] === "handoff_requested"),
74
82
  },
75
83
  "fail-run": revised,
76
84
  "cancel-run": revised,
@@ -5,6 +5,8 @@ export interface WorldRun {
5
5
  readonly status: string;
6
6
  readonly outcome?: string;
7
7
  readonly outcomeReason?: string;
8
+ readonly issueUpdate?: "failed";
9
+ readonly ticketStatus?: "complete" | "partial";
8
10
  readonly operationId?: string | null;
9
11
  readonly createdAt?: string;
10
12
  }
@@ -1,3 +1,4 @@
1
+ import { isTicketStatus } from "../ticket-status.js";
1
2
  import { gateContinuation } from "./gate-reduction.js";
2
3
  import { isBlockedReason } from "../workflow-contracts.js";
3
4
  import { continues } from "./gates.js";
@@ -113,7 +114,15 @@ function completionOutcome(world) {
113
114
  if (outcome !== "handoff_requested" && outcome !== "blocked")
114
115
  return {};
115
116
  if (outcome !== "blocked" || !isBlockedReason(outcomeReason))
116
- return { outcome };
117
+ return {
118
+ outcome,
119
+ ...(outcome === "handoff_requested" && world.issueUpdate === "failed"
120
+ ? { issueUpdate: world.issueUpdate }
121
+ : {}),
122
+ ...(outcome === "handoff_requested" && isTicketStatus(world.ticketStatus)
123
+ ? { ticketStatus: world.ticketStatus }
124
+ : {}),
125
+ };
117
126
  return { outcome, outcomeReason };
118
127
  }
119
128
  function noted(observed, evidence) {
@@ -40,7 +40,7 @@ export interface AuthorityEvent {
40
40
  readonly payload: Readonly<Record<string, string>>;
41
41
  }
42
42
  export declare const numberFields: readonly ["expectedRevision", "generation", "attempt", "limit", "effectLimit", "units", "recoveryLimit", "leaseGeneration", "sourceGeneration", "capacity"];
43
- export declare const textFields: readonly ["workflow", "submission", "input", "resolver", "gateId", "gateKind", "subject", "prompt", "expiresAt", "decision", "answer", "condition", "reason", "evidence", "retryAt", "operationId", "receipt", "capabilities", "capability", "method", "target", "payload", "inputDigest", "resource", "transport", "source", "worker", "outcome", "outcomeReason", "result"];
43
+ export declare const textFields: readonly ["workflow", "submission", "input", "resolver", "gateId", "gateKind", "subject", "prompt", "expiresAt", "decision", "answer", "condition", "reason", "evidence", "retryAt", "operationId", "receipt", "capabilities", "capability", "method", "target", "payload", "inputDigest", "resource", "transport", "source", "worker", "outcome", "outcomeReason", "ticketStatus", "issueUpdate", "result"];
44
44
  export type NumberField = (typeof numberFields)[number];
45
45
  export type TextField = (typeof textFields)[number];
46
46
  export type AuthorityCommand = {
@@ -60,6 +60,8 @@ export const textFields = [
60
60
  "worker",
61
61
  "outcome",
62
62
  "outcomeReason",
63
+ "ticketStatus",
64
+ "issueUpdate",
63
65
  "result",
64
66
  ];
65
67
  export const terminal = [
@@ -1,5 +1,6 @@
1
1
  import type { Database, JsonObject } from "solid-objects";
2
2
  import { type AuthorityEvent, type EffectIntent } from "../../authority/reducer.ts";
3
+ import { type ProjectionSnapshot } from "../../storage/decision-snapshots.ts";
3
4
  import type { AdmissionResult, ExecutionManifest } from "../../workflow-admission.ts";
4
5
  import { type StoredState } from "../authority-restore.ts";
5
6
  import { type SequenceChanges, type SequenceListener } from "./authority-changes.ts";
@@ -63,3 +64,4 @@ export interface SessionParts {
63
64
  readonly subscribe: (listener: SequenceListener) => () => void;
64
65
  }
65
66
  export declare function authoritySession(parts: SessionParts): Authority;
67
+ export declare function operationRecords(snapshot: Pick<ProjectionSnapshot, "outbox" | "deliveries">): readonly OperationRecord[];
@@ -221,16 +221,15 @@ async function healthyReceipt(session, value) {
221
221
  await refreshed(session);
222
222
  return receipt;
223
223
  }
224
- function operationRecords(snapshot) {
224
+ export function operationRecords(snapshot) {
225
+ const deliveries = Map.groupBy(snapshot.deliveries, (row) => row.operationId);
225
226
  return snapshot.outbox.map((row) => ({
226
227
  operationId: row.operationId,
227
228
  kind: intentKind(row.kind),
228
229
  runId: row.runId,
229
230
  createdSequence: row.createdSequence,
230
231
  releasedSequence: row.releasedSequence,
231
- deliveries: snapshot.deliveries
232
- .filter((delivery) => delivery.operationId === row.operationId)
233
- .map(({ effectId, outcome, receipt, purpose, epoch }) => ({
232
+ deliveries: (deliveries.get(row.operationId) ?? []).map(({ effectId, outcome, receipt, purpose, epoch }) => ({
234
233
  effectId,
235
234
  outcome,
236
235
  receipt,
@@ -0,0 +1,24 @@
1
+ import type { AnalysisResult } from "./analysis-contract.ts";
2
+ import { type AnalysisSnapshot } from "./analysis-input.ts";
3
+ export interface AnalysisBatchPosition {
4
+ readonly nextRecordIndex: number;
5
+ readonly completedBatchCount: number;
6
+ readonly previousResult: AnalysisResult | null;
7
+ }
8
+ export interface AnalysisBatch {
9
+ readonly kind: "batch";
10
+ readonly batchIndex: number;
11
+ readonly firstRecordIndex: number;
12
+ readonly nextRecordIndex: number;
13
+ readonly totalRecords: number;
14
+ readonly snapshot: AnalysisSnapshot;
15
+ readonly prompt: string;
16
+ readonly launchBytes: number;
17
+ readonly adapterDocument: string;
18
+ }
19
+ export type AnalysisBatchPreparation = AnalysisBatch | {
20
+ readonly kind: "refused";
21
+ readonly code: "analysis-input-refused" | "analysis-too-large" | "analysis-batch-limit";
22
+ };
23
+ export declare const analysisCaptureBytes = 52428800;
24
+ export declare function prepareAnalysisBatch(snapshot: AnalysisSnapshot, position: AnalysisBatchPosition): AnalysisBatchPreparation;
@@ -0,0 +1,83 @@
1
+ import { analysisDocumentBytes, analysisEvidence, isAnalysisSnapshot, validAnalysisImportId, } from "./analysis-input.js";
2
+ import { analysisLaunchBytes, analysisPrompt } from "./analysis-prompt.js";
3
+ export const analysisCaptureBytes = 52_428_800;
4
+ export function prepareAnalysisBatch(snapshot, position) {
5
+ if (!isAnalysisSnapshot(snapshot) ||
6
+ !validAnalysisImportId(snapshot.importId))
7
+ return { kind: "refused", code: "analysis-input-refused" };
8
+ if (Buffer.byteLength(snapshot.corpus) > analysisCaptureBytes ||
9
+ [snapshot.redaction, snapshot.structure].some((text) => Buffer.byteLength(text) > analysisDocumentBytes))
10
+ return { kind: "refused", code: "analysis-too-large" };
11
+ try {
12
+ return preparedBatch(snapshot, position);
13
+ }
14
+ catch {
15
+ return { kind: "refused", code: "analysis-input-refused" };
16
+ }
17
+ }
18
+ function preparedBatch(snapshot, position) {
19
+ const lines = snapshot.corpus.match(/[^\n]*\n|[^\n]+$/g) ?? [];
20
+ if (![position.nextRecordIndex, position.completedBatchCount].every(Number.isSafeInteger) ||
21
+ position.nextRecordIndex < 0 ||
22
+ position.nextRecordIndex >= lines.length ||
23
+ position.completedBatchCount < 0 ||
24
+ position.completedBatchCount > 4)
25
+ return { kind: "refused", code: "analysis-input-refused" };
26
+ if (position.completedBatchCount === 4)
27
+ return { kind: "refused", code: "analysis-batch-limit" };
28
+ const structure = analysisEvidence(snapshot).structure;
29
+ let longest = {
30
+ kind: "refused",
31
+ code: "analysis-too-large",
32
+ };
33
+ for (let end = position.nextRecordIndex + 1; end <= lines.length; end++) {
34
+ const batch = batchOf(snapshot, { lines, position, end, structure });
35
+ if (Math.max(batch.launchBytes, Buffer.byteLength(batch.adapterDocument)) >
36
+ analysisDocumentBytes)
37
+ break;
38
+ longest = batch;
39
+ }
40
+ return longest;
41
+ }
42
+ function batchOf(snapshot, input) {
43
+ const { position, lines, end } = input;
44
+ const batchSnapshot = {
45
+ ...snapshot,
46
+ corpus: lines.slice(position.nextRecordIndex, end).join(""),
47
+ structure: projectedStructure(snapshot.importId, input),
48
+ };
49
+ const context = {
50
+ batchIndex: position.completedBatchCount,
51
+ firstRecordIndex: position.nextRecordIndex,
52
+ previousResult: position.previousResult,
53
+ };
54
+ const prompt = analysisPrompt(batchSnapshot, context);
55
+ return {
56
+ kind: "batch",
57
+ batchIndex: context.batchIndex,
58
+ firstRecordIndex: context.firstRecordIndex,
59
+ nextRecordIndex: end,
60
+ totalRecords: lines.length,
61
+ snapshot: batchSnapshot,
62
+ prompt,
63
+ launchBytes: analysisLaunchBytes(prompt),
64
+ adapterDocument: JSON.stringify({ snapshot: batchSnapshot, context }),
65
+ };
66
+ }
67
+ function projectedStructure(importId, input) {
68
+ const { structure, position, end } = input;
69
+ return JSON.stringify({
70
+ schemaVersion: 1,
71
+ importId,
72
+ historyCompleteness: structure.historyCompleteness,
73
+ occurrences: structure.occurrences.slice(position.nextRecordIndex, end),
74
+ conflictGroups: structure.conflictGroups
75
+ .map((group) => ({
76
+ uuid: group.uuid,
77
+ variants: group.variants
78
+ .map((variant) => variant.filter((reference) => reference.recordIndex < end))
79
+ .filter((variant) => variant.length > 0),
80
+ }))
81
+ .filter((group) => group.variants.length > 0),
82
+ });
83
+ }