@knpkv/jira-clockify 1.3.0 → 1.4.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 (242) hide show
  1. package/README.md +245 -1
  2. package/dist/package.json +36 -12
  3. package/dist/src/agent/agentSettings.d.ts +18 -0
  4. package/dist/src/agent/agentSettings.d.ts.map +1 -0
  5. package/dist/src/agent/agentSettings.js +23 -0
  6. package/dist/src/agent/agentSettings.js.map +1 -0
  7. package/dist/src/agent/schedule.d.ts +20 -0
  8. package/dist/src/agent/schedule.d.ts.map +1 -0
  9. package/dist/src/agent/schedule.js +189 -0
  10. package/dist/src/agent/schedule.js.map +1 -0
  11. package/dist/src/agent/sessions.d.ts +427 -0
  12. package/dist/src/agent/sessions.d.ts.map +1 -0
  13. package/dist/src/agent/sessions.js +868 -0
  14. package/dist/src/agent/sessions.js.map +1 -0
  15. package/dist/src/agent/sourceConsumption.d.ts +79 -0
  16. package/dist/src/agent/sourceConsumption.d.ts.map +1 -0
  17. package/dist/src/agent/sourceConsumption.js +191 -0
  18. package/dist/src/agent/sourceConsumption.js.map +1 -0
  19. package/dist/src/agent/watch.d.ts +76 -0
  20. package/dist/src/agent/watch.d.ts.map +1 -0
  21. package/dist/src/agent/watch.js +45 -0
  22. package/dist/src/agent/watch.js.map +1 -0
  23. package/dist/src/agent/writePlanning.d.ts +154 -0
  24. package/dist/src/agent/writePlanning.d.ts.map +1 -0
  25. package/dist/src/agent/writePlanning.js +210 -0
  26. package/dist/src/agent/writePlanning.js.map +1 -0
  27. package/dist/src/bin.d.ts +3 -0
  28. package/dist/src/bin.d.ts.map +1 -0
  29. package/dist/src/bin.js +9 -3
  30. package/dist/src/bin.js.map +1 -1
  31. package/dist/src/cli/agentWrite.d.ts +164 -0
  32. package/dist/src/cli/agentWrite.d.ts.map +1 -0
  33. package/dist/src/cli/agentWrite.js +362 -0
  34. package/dist/src/cli/agentWrite.js.map +1 -0
  35. package/dist/src/cli/auth.d.ts +8 -0
  36. package/dist/src/cli/auth.d.ts.map +1 -0
  37. package/dist/src/cli/auth.js +10 -10
  38. package/dist/src/cli/auth.js.map +1 -1
  39. package/dist/src/cli/calendar.d.ts +42 -0
  40. package/dist/src/cli/calendar.d.ts.map +1 -0
  41. package/dist/src/cli/calendar.js +189 -0
  42. package/dist/src/cli/calendar.js.map +1 -0
  43. package/dist/src/cli/config.d.ts +12 -0
  44. package/dist/src/cli/config.d.ts.map +1 -0
  45. package/dist/src/cli/config.js +163 -21
  46. package/dist/src/cli/config.js.map +1 -1
  47. package/dist/src/cli/fetchTicket.d.ts +51 -0
  48. package/dist/src/cli/fetchTicket.d.ts.map +1 -0
  49. package/dist/src/cli/fetchTicket.js +18 -2
  50. package/dist/src/cli/fetchTicket.js.map +1 -1
  51. package/dist/src/cli/fuzzySelect.d.ts +24 -0
  52. package/dist/src/cli/fuzzySelect.d.ts.map +1 -0
  53. package/dist/src/cli/layers.d.ts +40 -0
  54. package/dist/src/cli/layers.d.ts.map +1 -0
  55. package/dist/src/cli/layers.js +56 -12
  56. package/dist/src/cli/layers.js.map +1 -1
  57. package/dist/src/cli/list.d.ts +8 -0
  58. package/dist/src/cli/list.d.ts.map +1 -0
  59. package/dist/src/cli/list.js +2 -2
  60. package/dist/src/cli/list.js.map +1 -1
  61. package/dist/src/cli/reconcile.d.ts +129 -0
  62. package/dist/src/cli/reconcile.d.ts.map +1 -0
  63. package/dist/src/cli/reconcile.js +635 -46
  64. package/dist/src/cli/reconcile.js.map +1 -1
  65. package/dist/src/cli/root.d.ts +15 -0
  66. package/dist/src/cli/root.d.ts.map +1 -0
  67. package/dist/src/cli/root.js +4 -3
  68. package/dist/src/cli/root.js.map +1 -1
  69. package/dist/src/cli/runtimeFailure.d.ts +10 -0
  70. package/dist/src/cli/runtimeFailure.d.ts.map +1 -0
  71. package/dist/src/cli/runtimeFailure.js +17 -0
  72. package/dist/src/cli/runtimeFailure.js.map +1 -0
  73. package/dist/src/cli/setup.d.ts +10 -0
  74. package/dist/src/cli/setup.d.ts.map +1 -0
  75. package/dist/src/cli/setup.js +7 -7
  76. package/dist/src/cli/setup.js.map +1 -1
  77. package/dist/src/cli/timer/discard.d.ts +4 -0
  78. package/dist/src/cli/timer/discard.d.ts.map +1 -0
  79. package/dist/src/cli/timer/discard.js +2 -2
  80. package/dist/src/cli/timer/discard.js.map +1 -1
  81. package/dist/src/cli/timer/edit.d.ts +22 -0
  82. package/dist/src/cli/timer/edit.d.ts.map +1 -0
  83. package/dist/src/cli/timer/edit.js +62 -42
  84. package/dist/src/cli/timer/edit.js.map +1 -1
  85. package/dist/src/cli/timer/index.d.ts +12 -0
  86. package/dist/src/cli/timer/index.d.ts.map +1 -0
  87. package/dist/src/cli/timer/log.d.ts +16 -0
  88. package/dist/src/cli/timer/log.d.ts.map +1 -0
  89. package/dist/src/cli/timer/log.js +6 -6
  90. package/dist/src/cli/timer/log.js.map +1 -1
  91. package/dist/src/cli/timer/start.d.ts +21 -0
  92. package/dist/src/cli/timer/start.d.ts.map +1 -0
  93. package/dist/src/cli/timer/start.js +9 -9
  94. package/dist/src/cli/timer/start.js.map +1 -1
  95. package/dist/src/cli/timer/status.d.ts +22 -0
  96. package/dist/src/cli/timer/status.d.ts.map +1 -0
  97. package/dist/src/cli/timer/status.js +3 -3
  98. package/dist/src/cli/timer/status.js.map +1 -1
  99. package/dist/src/cli/timer/stop.d.ts +48 -0
  100. package/dist/src/cli/timer/stop.d.ts.map +1 -0
  101. package/dist/src/cli/timer/stop.js +19 -19
  102. package/dist/src/cli/timer/stop.js.map +1 -1
  103. package/dist/src/cli/timer.d.ts +11 -0
  104. package/dist/src/cli/timer.d.ts.map +1 -0
  105. package/dist/src/cli/timer.js +1 -1
  106. package/dist/src/cli/timer.js.map +1 -1
  107. package/dist/src/cli/watch.d.ts +54 -0
  108. package/dist/src/cli/watch.d.ts.map +1 -0
  109. package/dist/src/cli/watch.js +412 -0
  110. package/dist/src/cli/watch.js.map +1 -0
  111. package/dist/src/cli/watchLease.d.ts +99 -0
  112. package/dist/src/cli/watchLease.d.ts.map +1 -0
  113. package/dist/src/cli/watchLease.js +204 -0
  114. package/dist/src/cli/watchLease.js.map +1 -0
  115. package/dist/src/cli/writerGuard.d.ts +37 -0
  116. package/dist/src/cli/writerGuard.d.ts.map +1 -0
  117. package/dist/src/cli/writerGuard.js +92 -0
  118. package/dist/src/cli/writerGuard.js.map +1 -0
  119. package/dist/src/index.d.ts +46 -0
  120. package/dist/src/index.d.ts.map +1 -0
  121. package/dist/src/index.js +46 -0
  122. package/dist/src/index.js.map +1 -0
  123. package/dist/src/main.d.ts +4 -0
  124. package/dist/src/main.d.ts.map +1 -0
  125. package/dist/src/services/AgentSessionReader.d.ts +121 -0
  126. package/dist/src/services/AgentSessionReader.d.ts.map +1 -0
  127. package/dist/src/services/AgentSessionReader.js +392 -0
  128. package/dist/src/services/AgentSessionReader.js.map +1 -0
  129. package/dist/src/services/ClockifyAuth.d.ts +47 -0
  130. package/dist/src/services/ClockifyAuth.d.ts.map +1 -0
  131. package/dist/src/services/ClockifyAuth.js +2 -2
  132. package/dist/src/services/ClockifyAuth.js.map +1 -1
  133. package/dist/src/services/CodexTranscript.d.ts +112 -0
  134. package/dist/src/services/CodexTranscript.d.ts.map +1 -0
  135. package/dist/src/services/CodexTranscript.js +96 -0
  136. package/dist/src/services/CodexTranscript.js.map +1 -0
  137. package/dist/src/services/ConfigService.d.ts +99 -0
  138. package/dist/src/services/ConfigService.d.ts.map +1 -0
  139. package/dist/src/services/ConfigService.js +79 -7
  140. package/dist/src/services/ConfigService.js.map +1 -1
  141. package/dist/src/services/HomeDirectory.d.ts +17 -0
  142. package/dist/src/services/HomeDirectory.d.ts.map +1 -0
  143. package/dist/src/services/HomeDirectory.js +1 -1
  144. package/dist/src/services/IssueFacts.d.ts +127 -0
  145. package/dist/src/services/IssueFacts.d.ts.map +1 -0
  146. package/dist/src/services/IssueFacts.js +277 -0
  147. package/dist/src/services/IssueFacts.js.map +1 -0
  148. package/dist/src/services/ProviderDeletion.d.ts +19 -0
  149. package/dist/src/services/ProviderDeletion.d.ts.map +1 -0
  150. package/dist/src/services/ProviderDeletion.js +37 -0
  151. package/dist/src/services/ProviderDeletion.js.map +1 -0
  152. package/dist/src/services/ReconcileService.d.ts +378 -0
  153. package/dist/src/services/ReconcileService.d.ts.map +1 -0
  154. package/dist/src/services/ReconcileService.js +1270 -79
  155. package/dist/src/services/ReconcileService.js.map +1 -1
  156. package/dist/src/services/SavedEntries.d.ts +40 -0
  157. package/dist/src/services/SavedEntries.d.ts.map +1 -0
  158. package/dist/src/services/SavedEntries.js +209 -0
  159. package/dist/src/services/SavedEntries.js.map +1 -0
  160. package/dist/src/services/SessionAttributor.d.ts +114 -0
  161. package/dist/src/services/SessionAttributor.d.ts.map +1 -0
  162. package/dist/src/services/SessionAttributor.js +240 -0
  163. package/dist/src/services/SessionAttributor.js.map +1 -0
  164. package/dist/src/services/SourceLedger.d.ts +104 -0
  165. package/dist/src/services/SourceLedger.d.ts.map +1 -0
  166. package/dist/src/services/SourceLedger.js +302 -0
  167. package/dist/src/services/SourceLedger.js.map +1 -0
  168. package/dist/src/services/StateWriter.d.ts +39 -0
  169. package/dist/src/services/StateWriter.d.ts.map +1 -0
  170. package/dist/src/services/TicketService.d.ts +63 -0
  171. package/dist/src/services/TicketService.d.ts.map +1 -0
  172. package/dist/src/services/TimerService.d.ts +145 -0
  173. package/dist/src/services/TimerService.d.ts.map +1 -0
  174. package/dist/src/services/TimerService.js +113 -80
  175. package/dist/src/services/TimerService.js.map +1 -1
  176. package/dist/src/services/internal/JiraWorklogPost.d.ts +7 -0
  177. package/dist/src/services/internal/JiraWorklogPost.d.ts.map +1 -0
  178. package/dist/src/services/internal/JiraWorklogPost.js +32 -0
  179. package/dist/src/services/internal/JiraWorklogPost.js.map +1 -0
  180. package/dist/src/testing/fakeHeadless.d.ts +318 -0
  181. package/dist/src/testing/fakeHeadless.d.ts.map +1 -0
  182. package/dist/src/testing/fakeHeadless.js +1149 -0
  183. package/dist/src/testing/fakeHeadless.js.map +1 -0
  184. package/dist/src/tui/App.d.ts +6 -0
  185. package/dist/src/tui/App.d.ts.map +1 -0
  186. package/dist/src/tui/App.js +1 -1
  187. package/dist/src/tui/App.js.map +1 -1
  188. package/dist/src/tui/atoms/runtime.d.ts +8 -0
  189. package/dist/src/tui/atoms/runtime.d.ts.map +1 -0
  190. package/dist/src/tui/atoms/runtime.js +1 -1
  191. package/dist/src/tui/atoms/runtime.js.map +1 -1
  192. package/dist/src/tui/atoms/tickets.d.ts +3 -0
  193. package/dist/src/tui/atoms/tickets.d.ts.map +1 -0
  194. package/dist/src/tui/atoms/timer.d.ts +11 -0
  195. package/dist/src/tui/atoms/timer.d.ts.map +1 -0
  196. package/dist/src/tui/atoms/timer.js +1 -1
  197. package/dist/src/tui/atoms/timer.js.map +1 -1
  198. package/dist/src/tui/atoms/ui.d.ts +11 -0
  199. package/dist/src/tui/atoms/ui.d.ts.map +1 -0
  200. package/dist/src/tui/atoms/ui.js +1 -1
  201. package/dist/src/tui/atoms/ui.js.map +1 -1
  202. package/dist/src/tui/components/BigTimer.d.ts +2 -0
  203. package/dist/src/tui/components/BigTimer.d.ts.map +1 -0
  204. package/dist/src/tui/components/Footer.d.ts +2 -0
  205. package/dist/src/tui/components/Footer.d.ts.map +1 -0
  206. package/dist/src/tui/components/Header.d.ts +2 -0
  207. package/dist/src/tui/components/Header.d.ts.map +1 -0
  208. package/dist/src/tui/components/Header.js +1 -1
  209. package/dist/src/tui/components/Header.js.map +1 -1
  210. package/dist/src/tui/components/PopupInput.d.ts +18 -0
  211. package/dist/src/tui/components/PopupInput.d.ts.map +1 -0
  212. package/dist/src/tui/components/PopupMessage.d.ts +20 -0
  213. package/dist/src/tui/components/PopupMessage.d.ts.map +1 -0
  214. package/dist/src/tui/components/TicketList.d.ts +2 -0
  215. package/dist/src/tui/components/TicketList.d.ts.map +1 -0
  216. package/dist/src/tui/components/TicketList.js +1 -1
  217. package/dist/src/tui/components/TicketList.js.map +1 -1
  218. package/dist/src/tui/components/TicketRow.d.ts +8 -0
  219. package/dist/src/tui/components/TicketRow.d.ts.map +1 -0
  220. package/dist/src/tui/components/TimerDisplay.d.ts +2 -0
  221. package/dist/src/tui/components/TimerDisplay.d.ts.map +1 -0
  222. package/dist/src/tui/components/index.d.ts +12 -0
  223. package/dist/src/tui/components/index.d.ts.map +1 -0
  224. package/dist/src/tui/context/theme.d.ts +15 -0
  225. package/dist/src/tui/context/theme.d.ts.map +1 -0
  226. package/dist/src/tui/hooks/useElapsedTimer.d.ts +6 -0
  227. package/dist/src/tui/hooks/useElapsedTimer.d.ts.map +1 -0
  228. package/dist/src/tui/hooks/useElapsedTimer.js +1 -1
  229. package/dist/src/tui/hooks/useElapsedTimer.js.map +1 -1
  230. package/dist/src/tui/hooks/useTerminalSize.d.ts +5 -0
  231. package/dist/src/tui/hooks/useTerminalSize.d.ts.map +1 -0
  232. package/dist/src/utils/hints.d.ts +12 -0
  233. package/dist/src/utils/hints.d.ts.map +1 -0
  234. package/dist/src/utils/hints.js +12 -0
  235. package/dist/src/utils/hints.js.map +1 -0
  236. package/dist/src/utils/time.d.ts +104 -0
  237. package/dist/src/utils/time.d.ts.map +1 -0
  238. package/dist/src/utils/time.js +74 -8
  239. package/dist/src/utils/time.js.map +1 -1
  240. package/dist/tsconfig.tsbuildinfo +1 -1
  241. package/package.json +36 -18
  242. package/skills/jcf/SKILL.md +93 -1
@@ -0,0 +1,204 @@
1
+ /**
2
+ * The lease that keeps two watches from writing the same hours, and the cursor that lets one resume.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **One writer per machine.** `jcf watch` derives what to write by subtracting what the two sides
7
+ * already hold. That makes a *later* look safe, and says nothing about a *simultaneous* one: two
8
+ * processes can read the same gap before either writes, and then both write it. An accidental
9
+ * second terminal is enough.
10
+ * - **Won by creating the file, not by reading it.** Acquisition is an exclusive create, so of two
11
+ * watches starting together exactly one succeeds. A read-then-write would let both conclude the
12
+ * lease was free.
13
+ * - **A held lease is immutable.** There is no safe read-then-overwrite takeover: two contenders can
14
+ * both read the same old value and each overwrite it. A lease is removed only by its owner during
15
+ * orderly shutdown. After an ungraceful process death it must be removed manually, after checking
16
+ * that no watch is running. Availability loses to duplicate provider writes here.
17
+ * - **A failure to claim is not a claim.** An unwritable config directory means no lease exists, not
18
+ * that this process holds one. It stops rather than running unprotected.
19
+ * - **The cursor is the earliest *unresolved* instant**, not where the holder's clock was. A watch
20
+ * stopped mid-block was holding prompts that had not settled; resuming from the shutdown time
21
+ * would filter exactly those out and lose the block it was trying to protect.
22
+ *
23
+ * **Gotchas**
24
+ *
25
+ * - Machine-local. Two watches on two machines against one Clockify account are not covered by this,
26
+ * and cannot be without an idempotency key the remote side honours.
27
+ * - A resume is offered only when the previous holder stopped *recently*. A cursor left behind
28
+ * yesterday describes work that has long since settled, and reaching back to it would be backfill
29
+ * wearing a cursor's clothes.
30
+ *
31
+ * @module
32
+ */
33
+ import * as Clock from "effect/Clock";
34
+ import * as Console from "effect/Console";
35
+ import * as Effect from "effect/Effect";
36
+ import * as FileSystem from "effect/FileSystem";
37
+ import * as Option from "effect/Option";
38
+ import * as Path from "effect/Path";
39
+ import * as Random from "effect/Random";
40
+ import * as Schema from "effect/Schema";
41
+ import { ConfigService } from "../services/ConfigService.js";
42
+ const LEASE_FILE = "watch.lease";
43
+ const CURSOR_FILE = "watch.cursor";
44
+ /** How many of the *holder's* poll intervals of silence make a lease abandoned rather than held. */
45
+ const STALE_INTERVALS = 3;
46
+ const Lease = Schema.Struct({
47
+ /** Who wrote this. The only way a watch can tell its own lease from one that replaced it. */
48
+ owner: Schema.optional(Schema.String),
49
+ heldSinceMs: Schema.Number,
50
+ /** Last time the holder said it was alive. */
51
+ refreshedAtMs: Schema.Number,
52
+ /** The holder's poll interval, so staleness is judged on its terms rather than a contender's. */
53
+ intervalSeconds: Schema.Number,
54
+ /** Wall clock at the holder's last look — how long it has been down, when it is. */
55
+ lookedUpToMs: Schema.optional(Schema.Number),
56
+ /** Earliest instant the holder had not yet resolved. Where a prompt resume must begin. */
57
+ unresolvedFromMs: Schema.optional(Schema.Number)
58
+ });
59
+ const decodeLease = Schema.decodeUnknownOption(Schema.fromJsonString(Lease));
60
+ const encode = (lease) => JSON.stringify(lease);
61
+ /**
62
+ * What a previous holder leaves for the next one, if anything.
63
+ *
64
+ * Two conditions, and both matter. The holder must have stopped recently — judged on *its* interval,
65
+ * since that is how often it was proving liveness — because a cursor from yesterday describes work
66
+ * that has settled and belongs to `reconcile`. And it must have said where it had got to; a lease
67
+ * written by an older version has no cursor and offers no resume.
68
+ */
69
+ const resumePoint = (previous, nowMs) => {
70
+ const downMs = nowMs - (previous.lookedUpToMs ?? previous.refreshedAtMs);
71
+ const graceMs = Math.max(previous.intervalSeconds, 1) * STALE_INTERVALS * 1000;
72
+ if (downMs > graceMs)
73
+ return null;
74
+ return previous.unresolvedFromMs ?? null;
75
+ };
76
+ /**
77
+ * Whether a failed exclusive create means "someone got there first" or "this cannot be written".
78
+ *
79
+ * The distinction is the difference between standing down and running unprotected. A read-only or
80
+ * full config directory used to look exactly like a lease already existing, and the fallback writes
81
+ * that followed were swallowed too — so the watch went on to write hours with no lease on disk at
82
+ * all, which is precisely the situation this file exists to prevent.
83
+ */
84
+ const isAlreadyExists = (error) => (error.message ?? "").includes("EEXIST") || (error.message ?? "").includes("AlreadyExists");
85
+ /**
86
+ * Take the lease, unless another watch holds it.
87
+ *
88
+ * The happy path is a single exclusive create: two watches starting together cannot both win it.
89
+ * An existing file is never overwritten. A stale-looking timestamp cannot make a read-then-write
90
+ * takeover atomic, so an ungraceful crash requires explicit human cleanup.
91
+ */
92
+ export const acquire = (options) => Effect.gen(function* () {
93
+ const fs = yield* FileSystem.FileSystem;
94
+ const path = yield* Path.Path;
95
+ const config = yield* ConfigService;
96
+ const dir = yield* config.configDir;
97
+ const file = path.join(dir, LEASE_FILE);
98
+ const cursorFile = path.join(dir, CURSOR_FILE);
99
+ const now = yield* Clock.currentTimeMillis;
100
+ // Two watches can start in the same millisecond, so the clock alone does not identify a holder.
101
+ const token = yield* Random.nextInt;
102
+ const owner = `${now.toString(36)}-${Math.abs(token).toString(36)}`;
103
+ yield* fs.makeDirectory(dir, { recursive: true }).pipe(Effect.catch(() => Effect.void));
104
+ const mine = {
105
+ owner,
106
+ heldSinceMs: now,
107
+ refreshedAtMs: now,
108
+ intervalSeconds: options.intervalSeconds,
109
+ lookedUpToMs: now,
110
+ unresolvedFromMs: now
111
+ };
112
+ // Read recovery state before taking ownership. Once exclusive creation succeeds there must be
113
+ // no interruptible work before returning the lease to the caller that installs its finalizer.
114
+ const previous = yield* fs.readFileString(cursorFile).pipe(Effect.map(decodeLease), Effect.catch(() => Effect.succeed(Option.none())));
115
+ const resumeFromMs = Option.match(previous, {
116
+ onNone: () => null,
117
+ onSome: (cursor) => resumePoint(cursor, now)
118
+ });
119
+ const created = yield* fs.writeFileString(file, encode(mine), { flag: "wx" }).pipe(Effect.as({ _tag: "Won" }), Effect.catch((error) => Effect.succeed(isAlreadyExists(error) ? { _tag: "Exists" } : { _tag: "Failed" }).pipe(Effect.tap(() => isAlreadyExists(error)
120
+ ? Effect.void
121
+ : Effect.logDebug(`Could not create ${file}: ${error.message}`)))));
122
+ if (created._tag === "Won") {
123
+ return { _tag: "Held", path: file, owner, resumeFromMs };
124
+ }
125
+ if (created._tag === "Failed") {
126
+ return {
127
+ _tag: "Unavailable",
128
+ reason: `could not write ${file} — nothing would stop a second watch writing the same hours`
129
+ };
130
+ }
131
+ const existing = yield* fs.readFileString(file).pipe(Effect.map(decodeLease),
132
+ // Unreadable or malformed is not evidence that anybody holds it.
133
+ Effect.catch(() => Effect.succeed(Option.none())));
134
+ const held = Option.getOrUndefined(existing);
135
+ if (held === undefined || held.owner === undefined) {
136
+ return {
137
+ _tag: "Unavailable",
138
+ reason: `could not verify ownership of existing ${file}`
139
+ };
140
+ }
141
+ return { _tag: "Taken", sinceMs: held.heldSinceMs };
142
+ });
143
+ /** Whether the lease on disk still carries this owner's name. */
144
+ const readStanding = (file, owner) => Effect.gen(function* () {
145
+ const fs = yield* FileSystem.FileSystem;
146
+ const current = yield* fs.readFileString(file).pipe(Effect.map((value) => ({ _tag: "Read", lease: decodeLease(value) })), Effect.catch((error) => Effect.succeed({
147
+ _tag: "Unavailable",
148
+ reason: `could not read ${file}: ${error.message}`
149
+ })));
150
+ if (current._tag === "Unavailable")
151
+ return current;
152
+ const lease = Option.getOrUndefined(current.lease);
153
+ if (lease === undefined || lease.owner === undefined) {
154
+ return {
155
+ _tag: "Unavailable",
156
+ reason: `could not verify ownership of ${file}`
157
+ };
158
+ }
159
+ return lease.owner === owner
160
+ ? { _tag: "Mine" }
161
+ : { _tag: "Lost", reason: "another jcf watch took over this lease" };
162
+ });
163
+ /**
164
+ * Verify that this process still owns the immutable lease.
165
+ *
166
+ * The cursor is persisted only on orderly release. Rewriting the lock while held would reintroduce
167
+ * a write race and buys no exclusion: ownership is the unchanged token on disk.
168
+ */
169
+ export const refresh = (options) => readStanding(options.path, options.owner);
170
+ /**
171
+ * Stop holding the lease, keeping the cursor in a separate file.
172
+ *
173
+ * Silent when the lease is no longer this run's. A displaced process must not remove another
174
+ * owner's lock or publish its cursor as the next run's resume boundary.
175
+ */
176
+ export const release = (options) => Effect.gen(function* () {
177
+ const fs = yield* FileSystem.FileSystem;
178
+ const path = yield* Path.Path;
179
+ const standing = yield* readStanding(options.path, options.owner);
180
+ if (standing._tag !== "Mine")
181
+ return;
182
+ const now = yield* Clock.currentTimeMillis;
183
+ const cursorPath = path.join(path.dirname(options.path), CURSOR_FILE);
184
+ const persisted = yield* fs.writeFileString(cursorPath, encode({
185
+ owner: options.owner,
186
+ heldSinceMs: options.heldSinceMs,
187
+ refreshedAtMs: 0,
188
+ intervalSeconds: options.intervalSeconds,
189
+ lookedUpToMs: now,
190
+ unresolvedFromMs: options.unresolvedFromMs
191
+ })).pipe(Effect.as(true), Effect.catch((error) => Console.error(`Could not persist the watch cursor at ${cursorPath}: ${error.message}. Lease retained.`).pipe(Effect.as(false))));
192
+ if (!persisted)
193
+ return;
194
+ yield* fs.remove(options.path).pipe(Effect.catch(() => Effect.void));
195
+ });
196
+ /** Release a short-lived non-watch writer without publishing a watch resume cursor. */
197
+ export const releaseGuard = (options) => Effect.gen(function* () {
198
+ const fs = yield* FileSystem.FileSystem;
199
+ const standing = yield* readStanding(options.path, options.owner);
200
+ if (standing._tag !== "Mine")
201
+ return;
202
+ yield* fs.remove(options.path).pipe(Effect.catch(() => Effect.void));
203
+ });
204
+ //# sourceMappingURL=watchLease.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watchLease.js","sourceRoot":"","sources":["../../../src/cli/watchLease.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import * as Effect from "effect/Effect";
2
+ import type * as FileSystem from "effect/FileSystem";
3
+ import type * as Path from "effect/Path";
4
+ import type { ConfigService } from "../services/ConfigService.js";
5
+ import * as WatchLease from "./watchLease.js";
6
+ declare const WriterGuardError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
7
+ readonly _tag: "WriterGuardError";
8
+ } & Readonly<A>;
9
+ /**
10
+ * Why a provider write could not run: `taken` means another JCF writer (usually `jcf watch`)
11
+ * holds the machine guard, `unavailable` that the guard could not be acquired, `lost` that the
12
+ * held guard stopped standing mid-operation, and `missing` that a mutation ran outside a guard.
13
+ */
14
+ export declare class WriterGuardError extends WriterGuardError_base<{
15
+ readonly reason: "taken" | "unavailable" | "lost" | "missing";
16
+ readonly message: string;
17
+ }> {
18
+ }
19
+ export declare const isWriterGuardError: (self: unknown) => self is {
20
+ _tag: "WriterGuardError";
21
+ };
22
+ type GuardRequirements = ConfigService | FileSystem.FileSystem | Path.Path;
23
+ /** Provide an already-held lease to nested writer services without acquiring it again. */
24
+ export declare const withExistingWriterGuard: <A, E, R>(lease: Extract<WatchLease.LeaseOutcome, {
25
+ readonly _tag: "Held";
26
+ }>, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
27
+ /** Acquire the machine guard, or reuse the live guard already enclosing this operation. */
28
+ export declare const withWriterGuard: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | WriterGuardError, R | GuardRequirements>;
29
+ /**
30
+ * Serialize one provider mutation inside the live guard.
31
+ *
32
+ * Same-fiber nesting reuses the permit. A fork gets a different fiber id and must wait for the
33
+ * shared semaphore; after the owning guard releases, the live-lease check fails closed.
34
+ */
35
+ export declare const mutate: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | WriterGuardError, R | GuardRequirements>;
36
+ export {};
37
+ //# sourceMappingURL=writerGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writerGuard.d.ts","sourceRoot":"","sources":["../../../src/cli/writerGuard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAIvC,OAAO,KAAK,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAEpD,OAAO,KAAK,KAAK,IAAI,MAAM,aAAa,CAAA;AAIxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;;;;AAE7C;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,sBAAqC;IACzE,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,aAAa,GAAG,MAAM,GAAG,SAAS,CAAA;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB,CAAC;CAAG;AAEL,eAAO,MAAM,kBAAkB;;CAAyC,CAAA;AAqBxE,KAAK,iBAAiB,GAAG,aAAa,GAAG,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAA;AAgB1E,0FAA0F;AAC1F,eAAO,MAAM,uBAAuB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,SACtC,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,UAC1D,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAuBrB,CAAA;AAEH,2FAA2F;AAC3F,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,UAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,gBAAgB,EAAE,CAAC,GAAG,iBAAiB,CAwB3D,CAAA;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,UACpB,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,gBAAgB,EAAE,CAAC,GAAG,iBAAiB,CA4B3D,CAAA"}
@@ -0,0 +1,92 @@
1
+ /** One live machine writer lease shared by every provider mutation in this process. */
2
+ import * as Context from "effect/Context";
3
+ import * as Data from "effect/Data";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Exit from "effect/Exit";
6
+ import * as Fiber from "effect/Fiber";
7
+ import * as FiberSet from "effect/FiberSet";
8
+ import * as Option from "effect/Option";
9
+ import * as Predicate from "effect/Predicate";
10
+ import * as Ref from "effect/Ref";
11
+ import * as Semaphore from "effect/Semaphore";
12
+ import * as WatchLease from "./watchLease.js";
13
+ /**
14
+ * Why a provider write could not run: `taken` means another JCF writer (usually `jcf watch`)
15
+ * holds the machine guard, `unavailable` that the guard could not be acquired, `lost` that the
16
+ * held guard stopped standing mid-operation, and `missing` that a mutation ran outside a guard.
17
+ */
18
+ export class WriterGuardError extends Data.TaggedError("WriterGuardError") {
19
+ }
20
+ export const isWriterGuardError = Predicate.isTagged("WriterGuardError");
21
+ class WriterScope extends Context.Service()("jcf/WriterScope") {
22
+ }
23
+ class MutationScope extends Context.Service()("jcf/WriterMutationScope") {
24
+ }
25
+ const standingError = (reason) => new WriterGuardError({ reason: "lost", message: `Machine writer guard lost: ${reason}` });
26
+ const verifyStanding = (scope) => Effect.gen(function* () {
27
+ const standing = yield* WatchLease.refresh({ path: scope.lease.path, owner: scope.lease.owner });
28
+ if (standing._tag !== "Mine")
29
+ return yield* standingError(standing.reason);
30
+ });
31
+ const verify = (scope) => Effect.gen(function* () {
32
+ if (!(yield* Ref.get(scope.active))) {
33
+ return yield* standingError("the owning operation already released it");
34
+ }
35
+ yield* verifyStanding(scope);
36
+ });
37
+ /** Provide an already-held lease to nested writer services without acquiring it again. */
38
+ export const withExistingWriterGuard = (lease, effect) => Effect.scoped(Effect.gen(function* () {
39
+ const fibers = yield* FiberSet.make();
40
+ const cancelAndJoin = FiberSet.clear(fibers);
41
+ const drain = FiberSet.awaitEmpty(fibers).pipe(Effect.interruptible, Effect.onInterrupt(() => cancelAndJoin));
42
+ return yield* Effect.acquireUseRelease(Effect.all({ active: Ref.make(true), admission: Semaphore.make(1), mutations: Semaphore.make(1) }), ({ active, admission, mutations }) => effect.pipe(Effect.provideService(WriterScope, { active, admission, fibers, lease, mutations })), ({ active, admission }, exit) => Ref.set(active, false).pipe(
43
+ // Flush any admission already between the active check and FiberSet registration.
44
+ Effect.andThen(admission.withPermit(Effect.void)), Effect.andThen(Exit.isSuccess(exit) ? drain : cancelAndJoin)));
45
+ }));
46
+ /** Acquire the machine guard, or reuse the live guard already enclosing this operation. */
47
+ export const withWriterGuard = (effect) => Effect.gen(function* () {
48
+ const current = yield* Effect.serviceOption(WriterScope);
49
+ if (Option.isSome(current)) {
50
+ yield* verify(current.value);
51
+ return yield* effect;
52
+ }
53
+ return yield* Effect.acquireUseRelease(WatchLease.acquire({ intervalSeconds: 300 }), (lease) => Effect.gen(function* () {
54
+ if (lease._tag === "Taken") {
55
+ return yield* new WriterGuardError({
56
+ reason: "taken",
57
+ message: "Another JCF writer holds the machine guard"
58
+ });
59
+ }
60
+ if (lease._tag === "Unavailable") {
61
+ return yield* new WriterGuardError({ reason: "unavailable", message: lease.reason });
62
+ }
63
+ return yield* withExistingWriterGuard(lease, effect);
64
+ }), (lease) => lease._tag === "Held" ? WatchLease.releaseGuard(lease) : Effect.void);
65
+ });
66
+ /**
67
+ * Serialize one provider mutation inside the live guard.
68
+ *
69
+ * Same-fiber nesting reuses the permit. A fork gets a different fiber id and must wait for the
70
+ * shared semaphore; after the owning guard releases, the live-lease check fails closed.
71
+ */
72
+ export const mutate = (effect) => Effect.gen(function* () {
73
+ const ownerFiber = yield* Effect.fiberId;
74
+ const mutation = yield* Effect.serviceOption(MutationScope);
75
+ const writer = yield* Effect.serviceOption(WriterScope);
76
+ if (Option.isSome(mutation) && mutation.value.ownerFiber === ownerFiber) {
77
+ if (Option.isNone(writer)) {
78
+ return yield* new WriterGuardError({ reason: "missing", message: "Writer authority is missing" });
79
+ }
80
+ yield* verifyStanding(writer.value);
81
+ return yield* effect;
82
+ }
83
+ if (Option.isNone(writer))
84
+ return yield* withWriterGuard(mutate(effect));
85
+ const fiber = yield* writer.value.admission.withPermit(verify(writer.value).pipe(Effect.andThen(FiberSet.run(writer.value.fibers, writer.value.mutations.withPermit(Effect.gen(function* () {
86
+ yield* verifyStanding(writer.value);
87
+ const mutationFiber = yield* Effect.fiberId;
88
+ return yield* effect.pipe(Effect.provideService(MutationScope, { ownerFiber: mutationFiber }));
89
+ }))))));
90
+ return yield* Fiber.join(fiber);
91
+ });
92
+ //# sourceMappingURL=writerGuard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writerGuard.js","sourceRoot":"","sources":["../../../src/cli/writerGuard.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AACnC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AACnC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAE3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,SAAS,MAAM,kBAAkB,CAAA;AAC7C,OAAO,KAAK,GAAG,MAAM,YAAY,CAAA;AACjC,OAAO,KAAK,SAAS,MAAM,kBAAkB,CAAA;AAE7C,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;;GAIG;AACH,MAAM,OAAO,gBAAiB,SAAQ,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAGvE;CAAG;AAEL,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAA;AAUxE,MAAM,WAAY,SAAQ,OAAO,CAAC,OAAO,EAAiC,CAAC,iBAAiB,CAAC;CAAG;AAMhG,MAAM,aAAc,SAAQ,OAAO,CAAC,OAAO,EAAqC,CAAC,yBAAyB,CAAC;CAAG;AAE9G,MAAM,aAAa,GAAG,CAAC,MAAc,EAAE,EAAE,CACvC,IAAI,gBAAgB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,8BAA8B,MAAM,EAAE,EAAE,CAAC,CAAA;AAI3F,MAAM,cAAc,GAAG,CAAC,KAAuB,EAAgE,EAAE,CAC/G,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;IAChG,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AAC5E,CAAC,CAAC,CAAA;AAEJ,MAAM,MAAM,GAAG,CAAC,KAAuB,EAAgE,EAAE,CACvG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QACpC,OAAO,KAAK,CAAC,CAAC,aAAa,CAAC,0CAA0C,CAAC,CAAA;IACzE,CAAC;IACD,KAAK,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC,CAAC,CAAA;AAEJ,0FAA0F;AAC1F,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,KAAkE,EAClE,MAA8B,EACN,EAAE,CAC1B,MAAM,CAAC,MAAM,CACX,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAoB,CAAA;IACvD,MAAM,aAAa,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAC5C,MAAM,CAAC,aAAa,EACpB,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,CACxC,CAAA;IACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,iBAAiB,CACpC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAClG,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,CACnC,MAAM,CAAC,IAAI,CACT,MAAM,CAAC,cAAc,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CACpF,EACH,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,EAAE,CAC9B,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,IAAI;IACzB,kFAAkF;IAClF,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EACjD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAC7D,CACJ,CAAA;AACH,CAAC,CAAC,CACH,CAAA;AAEH,2FAA2F;AAC3F,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,MAA8B,EACiC,EAAE,CACjE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAA;IACxD,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC5B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAA;IACtB,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,iBAAiB,CACpC,UAAU,CAAC,OAAO,CAAC,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,EAC5C,CAAC,KAAK,EAAE,EAAE,CACR,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC;gBACjC,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,4CAA4C;aACtD,CAAC,CAAA;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;QACtF,CAAC;QACD,OAAO,KAAK,CAAC,CAAC,uBAAuB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACtD,CAAC,CAAC,EACJ,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAChF,CAAA;AACH,CAAC,CAAC,CAAA;AAEJ;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,MAA8B,EACiC,EAAE,CACjE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAA;IACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,CAAA;IAC3D,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAA;IACvD,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QACxE,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC,CAAA;QACnG,CAAC;QACD,KAAK,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACnC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAA;IACtB,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;IACxE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,UAAU,CACpD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CACvB,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CACzB,MAAM,CAAC,KAAK,CAAC,MAAM,EACnB,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,UAAU,CAC/B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,KAAK,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACnC,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAA;QAC3C,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,CAAC,CAAA;IAChG,CAAC,CAAC,CACH,CACF,CAAC,CACH,CACF,CAAA;IACD,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACjC,CAAC,CAAC,CAAA"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `@knpkv/jira-clockify` — the headless engine behind `jcf`.
3
+ *
4
+ * Namespace re-exports for clean imports.
5
+ *
6
+ * **What is here, and what is not**
7
+ *
8
+ * Everything a *second* surface needs to derive Proposed Worklogs and write a confirmed one: the
9
+ * pure agent-session core, the services, the live layer composition, the write path, and the
10
+ * calendar. Deliberately absent are the command definitions and the TUI — a consumer builds its own
11
+ * surface, and importing `effect/cli` or `@opentui/react` to reach a service would be a
12
+ * cost paid for nothing. The test seam lives at `@knpkv/jira-clockify/testing.js`, out of this
13
+ * barrel so nothing production imports it by accident.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { AgentWrite, Layers, ReconcileService, Time } from "@knpkv/jira-clockify"
18
+ *
19
+ * const week = Time.isoWeekPeriod(new Date())
20
+ * const report = yield* (yield* ReconcileService.ReconcileService).proposeFromSessions(week)
21
+ * ```
22
+ *
23
+ * @module
24
+ */
25
+ export * as AgentSessions from "./agent/sessions.js";
26
+ export * as SourceConsumption from "./agent/sourceConsumption.js";
27
+ export * as AgentWatch from "./agent/watch.js";
28
+ export * as AgentWrite from "./cli/agentWrite.js";
29
+ export * as Calendar from "./cli/calendar.js";
30
+ export * as FetchTicket from "./cli/fetchTicket.js";
31
+ export * as Layers from "./cli/layers.js";
32
+ export * as WatchLease from "./cli/watchLease.js";
33
+ export * as WriterGuard from "./cli/writerGuard.js";
34
+ export * as AgentSessionReader from "./services/AgentSessionReader.js";
35
+ export * as ClockifyAuth from "./services/ClockifyAuth.js";
36
+ export * as ConfigService from "./services/ConfigService.js";
37
+ export * as HomeDirectory from "./services/HomeDirectory.js";
38
+ export * as IssueFacts from "./services/IssueFacts.js";
39
+ export * as ReconcileService from "./services/ReconcileService.js";
40
+ export * as SavedEntries from "./services/SavedEntries.js";
41
+ export * as SessionAttributor from "./services/SessionAttributor.js";
42
+ export * as StateWriter from "./services/StateWriter.js";
43
+ export * as TicketService from "./services/TicketService.js";
44
+ export * as TimerService from "./services/TimerService.js";
45
+ export * as Time from "./utils/time.js";
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,aAAa,MAAM,qBAAqB,CAAA;AACpD,OAAO,KAAK,iBAAiB,MAAM,8BAA8B,CAAA;AACjE,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,UAAU,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,QAAQ,MAAM,mBAAmB,CAAA;AAC7C,OAAO,KAAK,WAAW,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AACzC,OAAO,KAAK,UAAU,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,WAAW,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,kBAAkB,MAAM,kCAAkC,CAAA;AACtE,OAAO,KAAK,YAAY,MAAM,4BAA4B,CAAA;AAC1D,OAAO,KAAK,aAAa,MAAM,6BAA6B,CAAA;AAC5D,OAAO,KAAK,aAAa,MAAM,6BAA6B,CAAA;AAC5D,OAAO,KAAK,UAAU,MAAM,0BAA0B,CAAA;AACtD,OAAO,KAAK,gBAAgB,MAAM,gCAAgC,CAAA;AAClE,OAAO,KAAK,YAAY,MAAM,4BAA4B,CAAA;AAC1D,OAAO,KAAK,iBAAiB,MAAM,iCAAiC,CAAA;AACpE,OAAO,KAAK,WAAW,MAAM,2BAA2B,CAAA;AACxD,OAAO,KAAK,aAAa,MAAM,6BAA6B,CAAA;AAC5D,OAAO,KAAK,YAAY,MAAM,4BAA4B,CAAA;AAC1D,OAAO,KAAK,IAAI,MAAM,iBAAiB,CAAA"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `@knpkv/jira-clockify` — the headless engine behind `jcf`.
3
+ *
4
+ * Namespace re-exports for clean imports.
5
+ *
6
+ * **What is here, and what is not**
7
+ *
8
+ * Everything a *second* surface needs to derive Proposed Worklogs and write a confirmed one: the
9
+ * pure agent-session core, the services, the live layer composition, the write path, and the
10
+ * calendar. Deliberately absent are the command definitions and the TUI — a consumer builds its own
11
+ * surface, and importing `effect/cli` or `@opentui/react` to reach a service would be a
12
+ * cost paid for nothing. The test seam lives at `@knpkv/jira-clockify/testing.js`, out of this
13
+ * barrel so nothing production imports it by accident.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { AgentWrite, Layers, ReconcileService, Time } from "@knpkv/jira-clockify"
18
+ *
19
+ * const week = Time.isoWeekPeriod(new Date())
20
+ * const report = yield* (yield* ReconcileService.ReconcileService).proposeFromSessions(week)
21
+ * ```
22
+ *
23
+ * @module
24
+ */
25
+ export * as AgentSessions from "./agent/sessions.js";
26
+ export * as SourceConsumption from "./agent/sourceConsumption.js";
27
+ export * as AgentWatch from "./agent/watch.js";
28
+ export * as AgentWrite from "./cli/agentWrite.js";
29
+ export * as Calendar from "./cli/calendar.js";
30
+ export * as FetchTicket from "./cli/fetchTicket.js";
31
+ export * as Layers from "./cli/layers.js";
32
+ export * as WatchLease from "./cli/watchLease.js";
33
+ export * as WriterGuard from "./cli/writerGuard.js";
34
+ export * as AgentSessionReader from "./services/AgentSessionReader.js";
35
+ export * as ClockifyAuth from "./services/ClockifyAuth.js";
36
+ export * as ConfigService from "./services/ConfigService.js";
37
+ export * as HomeDirectory from "./services/HomeDirectory.js";
38
+ export * as IssueFacts from "./services/IssueFacts.js";
39
+ export * as ReconcileService from "./services/ReconcileService.js";
40
+ export * as SavedEntries from "./services/SavedEntries.js";
41
+ export * as SessionAttributor from "./services/SessionAttributor.js";
42
+ export * as StateWriter from "./services/StateWriter.js";
43
+ export * as TicketService from "./services/TicketService.js";
44
+ export * as TimerService from "./services/TimerService.js";
45
+ export * as Time from "./utils/time.js";
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,aAAa,MAAM,qBAAqB,CAAA;AACpD,OAAO,KAAK,iBAAiB,MAAM,8BAA8B,CAAA;AACjE,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,UAAU,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,QAAQ,MAAM,mBAAmB,CAAA;AAC7C,OAAO,KAAK,WAAW,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AACzC,OAAO,KAAK,UAAU,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,WAAW,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,kBAAkB,MAAM,kCAAkC,CAAA;AACtE,OAAO,KAAK,YAAY,MAAM,4BAA4B,CAAA;AAC1D,OAAO,KAAK,aAAa,MAAM,6BAA6B,CAAA;AAC5D,OAAO,KAAK,aAAa,MAAM,6BAA6B,CAAA;AAC5D,OAAO,KAAK,UAAU,MAAM,0BAA0B,CAAA;AACtD,OAAO,KAAK,gBAAgB,MAAM,gCAAgC,CAAA;AAClE,OAAO,KAAK,YAAY,MAAM,4BAA4B,CAAA;AAC1D,OAAO,KAAK,iBAAiB,MAAM,iCAAiC,CAAA;AACpE,OAAO,KAAK,WAAW,MAAM,2BAA2B,CAAA;AACxD,OAAO,KAAK,aAAa,MAAM,6BAA6B,CAAA;AAC5D,OAAO,KAAK,YAAY,MAAM,4BAA4B,CAAA;AAC1D,OAAO,KAAK,IAAI,MAAM,iBAAiB,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { Effect } from "effect";
2
+ declare const program: Effect.Effect<void, never, never>;
3
+ export default program;
4
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAY,MAAM,EAAE,MAAM,QAAQ,CAAA;AAYzC,QAAA,MAAM,OAAO,mCA4BS,CAAA;eAEP,OAAO"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Read local Claude Code and Codex Agent Sessions as reconciliation evidence.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Read-only evidence**: transcripts are never written, moved, or modified. This service only
7
+ * reports what a session touched, when, and where — see ADR-0006.
8
+ * - **Opt-in scope**: a session becomes evidence only when its working directory sits inside a
9
+ * configured Session Root. Claude project names allow a pre-read filter; Codex date directories
10
+ * do not, so Codex working directories are checked after local decoding. Out-of-scope text never
11
+ * reaches ticket mining, a Coding Agent or a proposal.
12
+ * - **Tolerant decoding**: the transcript layout is an external contract that changes without
13
+ * notice. Unrecognised and malformed lines are skipped; a session survives on the lines it can
14
+ * decode rather than failing the whole run.
15
+ * - **Presence, not busy-ness**: only messages the *person* typed count as Session Activity. A
16
+ * transcript is overwhelmingly the agent's own output — measured on one real day, 1641 events of
17
+ * which 66 were human — so counting every event measures how long the agent was busy, which is
18
+ * not the same as how long anyone was working.
19
+ *
20
+ * **Gotchas**
21
+ *
22
+ * - A `user` line is not necessarily a person. Tool results come back as `user` messages whose
23
+ * content is `tool_result` blocks, and they outnumber real prompts roughly ten to one. Only text
24
+ * content counts.
25
+ * - Assistant output, sidechain turns, and tool results still feed the candidate Issue Keys and the
26
+ * digest: they say nothing about presence, but plenty about *what* the session was for.
27
+ * - The working directory and branch are taken from the session's last in-window line of any kind —
28
+ * the state the credited work actually ran under.
29
+ *
30
+ * @module
31
+ */
32
+ import * as Context from "effect/Context";
33
+ import * as Effect from "effect/Effect";
34
+ import * as FileSystem from "effect/FileSystem";
35
+ import * as Layer from "effect/Layer";
36
+ import * as Path from "effect/Path";
37
+ import { type AttributableSession, type SessionActivity } from "../agent/sessions.js";
38
+ import { ConfigService } from "./ConfigService.js";
39
+ import { HomeDirectory } from "./HomeDirectory.js";
40
+ import type { ReconcilePeriod } from "./ReconcileService.js";
41
+ /** One Agent Session, reduced to the evidence attribution and partitioning need. */
42
+ export interface AgentSessionRecord extends AttributableSession {
43
+ /** Session Activity inside the requested window, ascending. */
44
+ readonly activity: ReadonlyArray<SessionActivity>;
45
+ /** Bounded digest of the session's prompts, for a Coding Agent to read. */
46
+ readonly digest: string;
47
+ /**
48
+ * Where this stretch of the transcript ended, when something followed it under a different branch
49
+ * or directory. Presence after its final prompt stops here rather than running the full Idle Cap.
50
+ */
51
+ readonly boundedAtMs: number | null;
52
+ }
53
+ declare const AgentSessionError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
54
+ readonly _tag: "AgentSessionError";
55
+ } & Readonly<A>;
56
+ export declare class AgentSessionError extends AgentSessionError_base<{
57
+ readonly message: string;
58
+ readonly cause?: unknown;
59
+ }> {
60
+ }
61
+ export interface AgentSessionReaderContract {
62
+ /**
63
+ * Every in-scope Agent Session with activity inside the period. Codex files are decoded locally
64
+ * to check their working directories; only in-scope segments become evidence.
65
+ */
66
+ readonly read: (period: ReconcilePeriod) => Effect.Effect<ReadonlyArray<AgentSessionRecord>, AgentSessionError>;
67
+ }
68
+ declare const AgentSessionReader_base: Context.ServiceClass<AgentSessionReader, "jcf/AgentSessionReader", AgentSessionReaderContract>;
69
+ export declare class AgentSessionReader extends AgentSessionReader_base {
70
+ }
71
+ /** What one transcript file contributes, before scope and window filtering. */
72
+ interface DecodedTranscript {
73
+ readonly sessionId: string;
74
+ readonly cwd: string;
75
+ readonly gitBranch: string | null;
76
+ readonly activity: ReadonlyArray<SessionActivity>;
77
+ /** Where this segment gave way to the next, or null when nothing followed it. */
78
+ readonly boundedAtMs: number | null;
79
+ /** Prompt text in transcript order — mined for candidate keys and folded into the digest. */
80
+ readonly texts: ReadonlyArray<string>;
81
+ }
82
+ /**
83
+ * Decode one transcript into the evidence it holds, keeping only activity inside `[from, to)`.
84
+ *
85
+ * Returns one entry per *segment* — a stretch of the transcript that ran under one working
86
+ * directory and one branch. A session that switches branch mid-run is two pieces of work, and
87
+ * taking the last line's branch for all of it would credit the morning's prompts to the afternoon's
88
+ * ticket. Under `jcf watch` that is worse than a misattribution: the morning can already have been
89
+ * written under the first ticket before the switch, and would then be derived again under the
90
+ * second, putting the same wall clock on two tickets.
91
+ *
92
+ * Segments that resolve to the same Issue Key are unioned again downstream, so splitting costs
93
+ * nothing when a branch change does not change the work. What it does cost is the gap *across* a
94
+ * switch, which is no longer bridged — an under-count of at most one Idle Cap, which is the
95
+ * direction this design prefers to be wrong in.
96
+ *
97
+ * Pure and total: a malformed line, an unparseable timestamp, or a file of pure noise yields no
98
+ * segments rather than an error.
99
+ */
100
+ export declare const decodeTranscript: (content: string, period: {
101
+ readonly fromMs: number;
102
+ readonly toMs: number;
103
+ }) => ReadonlyArray<DecodedTranscript>;
104
+ /**
105
+ * True when a project directory could hold a session inside one of the Session Roots.
106
+ *
107
+ * A *filter*, not a decision. The encoding is many-to-one — a root `/a/b-c` and an out-of-root
108
+ * `/a/b/c` both become `-a-b-c` — so a directory it admits may still turn out to be elsewhere, and
109
+ * the authoritative check on the decoded `cwd` runs afterwards regardless. What it cannot do is
110
+ * exclude one wrongly: separators encode character-by-character after a trailing root separator
111
+ * is removed, and Windows root spelling is folded. The decoded `cwd` still decides scope.
112
+ *
113
+ * So the guarantee is bounded, and worth stating exactly: a transcript whose project directory
114
+ * cannot encode from any Session Root is never opened, which in a working set of any size is nearly
115
+ * all of them. A transcript that collides with a root's encoding is opened and then discarded
116
+ * unread — the honest limit of deciding scope from a lossy directory name.
117
+ */
118
+ export declare const mayHoldSessionRoot: (projectDir: string, roots: ReadonlyArray<string>) => boolean;
119
+ export declare const layer: Layer.Layer<AgentSessionReader, never, ConfigService | FileSystem.FileSystem | HomeDirectory | Path.Path>;
120
+ export {};
121
+ //# sourceMappingURL=AgentSessionReader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentSessionReader.d.ts","sourceRoot":"","sources":["../../../src/services/AgentSessionReader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AAInC,OAAO,EACL,KAAK,mBAAmB,EAKxB,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAM5D,oFAAoF;AACpF,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,eAAe,CAAC,CAAA;IACjD,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CACpC;;;;AAED,qBAAa,iBAAkB,SAAQ,uBAAsC;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CACzB,CAAC;CAAG;AAEL,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,EAAE,iBAAiB,CAAC,CAAA;CAChH;;AAED,qBAAa,kBAAmB,SAAQ,uBAEvC;CAAG;AA6HJ,+EAA+E;AAC/E,UAAU,iBAAiB;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,eAAe,CAAC,CAAA;IACjD,iFAAiF;IACjF,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,6FAA6F;IAC7F,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACtC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,gBAAgB,YAClB,MAAM,UACP;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KACzD,aAAa,CAAC,iBAAiB,CAAkE,CAAA;AA8FpG;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,eAAgB,MAAM,SAAS,aAAa,CAAC,MAAM,CAAC,KAAG,OAQlF,CAAA;AAEJ,eAAO,MAAM,KAAK,2GA4IjB,CAAA"}