@knpkv/jira-clockify 1.2.1 → 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 (243) hide show
  1. package/README.md +254 -3
  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 +110 -71
  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/nvim/lua/jcf/state.lua +191 -13
  242. package/package.json +36 -18
  243. package/skills/jcf/SKILL.md +93 -1
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Who an Issue Key belongs to, and what it is called.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **A branch cannot tell authoring from reviewing.** Checking out a colleague's PR puts their
7
+ * Issue Key on the branch, and branch attribution then offers their ticket as your work. Jira
8
+ * knows the difference, so ownership is asked of Jira rather than guessed from the transcript.
9
+ * - **One search, not one request per key.** A week of rows on six tickets costs a single
10
+ * `key in (…)` search, which also returns the titles — an Issue Key alone is not a timesheet
11
+ * anybody can read six months later.
12
+ * - **Cached to disk, because neither fact moves quickly.** A title almost never changes and an
13
+ * assignee rarely does, so a week already looked at answers from `~/.jcf/issues.json` and a
14
+ * Jira outage costs nothing. Entries past {@link OWNERSHIP_TTL_MS} are re-asked.
15
+ * - **Unknown is not "not yours".** Every failure — no login, an unreachable site, an issue in a
16
+ * project you cannot see — leaves a key with no fact. A caller may hide what Jira says belongs to
17
+ * someone else; it must never hide what Jira was not asked about, because that would silently drop
18
+ * hours nobody could then find.
19
+ *
20
+ * @module
21
+ */
22
+ import { JiraApiClient } from "@knpkv/jira-api-client";
23
+ import { JiraAuth } from "@knpkv/jira-cli/JiraAuth";
24
+ import * as Clock from "effect/Clock";
25
+ import * as Context from "effect/Context";
26
+ import * as Effect from "effect/Effect";
27
+ import * as FileSystem from "effect/FileSystem";
28
+ import * as Layer from "effect/Layer";
29
+ import * as Option from "effect/Option";
30
+ import * as Path from "effect/Path";
31
+ import * as Predicate from "effect/Predicate";
32
+ import * as Schema from "effect/Schema";
33
+ import { isTicketKey } from "../agent/sessions.js";
34
+ import { HomeDirectory } from "./HomeDirectory.js";
35
+ export class IssueFacts extends Context.Service()("jcf/IssueFacts") {
36
+ }
37
+ /** Admit unknown tickets, but withhold a positive other-owner fact unless explicitly overridden. */
38
+ export const isOwnedByMe = (ticketKey, policy) => {
39
+ if (policy.mode === "any" || policy.overrides.includes(ticketKey))
40
+ return true;
41
+ const fact = policy.facts.get(ticketKey);
42
+ return fact === undefined || fact.mine !== false;
43
+ };
44
+ /**
45
+ * How long an ownership answer is trusted before it is asked again.
46
+ *
47
+ * Twelve hours, which is a working day: a ticket reassigned this morning is right by tomorrow, and
48
+ * nothing here is load-bearing enough to justify a request per page load. A title is cached under
49
+ * the same clock only because it travels in the same response.
50
+ */
51
+ export const OWNERSHIP_TTL_MS = 12 * 60 * 60 * 1000;
52
+ /** Keys per search. JQL goes in a query string, so a hundred keys at once is a 414 waiting to happen. */
53
+ const KEYS_PER_SEARCH = 50;
54
+ export const emptyCache = { accountId: null, cloudId: null, issues: new Map() };
55
+ const CACHE_DIR = ".jcf";
56
+ const CACHE_FILE = "issues.json";
57
+ const readJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Json));
58
+ const JsonObject = Schema.Record(Schema.String, Schema.Json);
59
+ const isJsonObject = Schema.is(JsonObject);
60
+ const nested = (fields, key, field) => {
61
+ const value = fields[key];
62
+ if (!isJsonObject(value))
63
+ return null;
64
+ const inner = value[field];
65
+ return Predicate.isString(inner) ? inner : null;
66
+ };
67
+ const assignment = (assignee, myAccountId) => {
68
+ if (assignee === null)
69
+ return "unassigned";
70
+ if (!isJsonObject(assignee) || myAccountId === null)
71
+ return "unknown";
72
+ const accountId = assignee["accountId"];
73
+ if (!Predicate.isString(accountId))
74
+ return "unknown";
75
+ return accountId === myAccountId ? "mine" : "other-assignee";
76
+ };
77
+ const ownership = (assignment) => assignment === "mine" ? true : assignment === "unknown" ? null : false;
78
+ /**
79
+ * Shape the issues one search returned into facts, deciding ownership by account id.
80
+ *
81
+ * By account id and never by display name: two people can share a name, a name can be changed, and
82
+ * a display name is absent entirely on a site that hides profiles. `mine` is false for an
83
+ * unassigned issue — nobody owns it, so nothing about it is evidence that you do.
84
+ */
85
+ export const factsFromIssues = (issues, myAccountId) => {
86
+ const facts = new Map();
87
+ for (const issue of issues) {
88
+ if (!isJsonObject(issue))
89
+ continue;
90
+ const key = issue["key"];
91
+ if (!Predicate.isString(key))
92
+ continue;
93
+ const rawFields = issue["fields"];
94
+ const fields = isJsonObject(rawFields) ? rawFields : {};
95
+ const title = fields["summary"];
96
+ const rawAssignee = fields["assignee"];
97
+ const assigned = assignment(rawAssignee, myAccountId);
98
+ facts.set(key, {
99
+ assignee: nested(fields, "assignee", "displayName"),
100
+ assignment: assigned,
101
+ key,
102
+ mine: ownership(assigned),
103
+ title: Predicate.isString(title) ? title : null
104
+ });
105
+ }
106
+ return facts;
107
+ };
108
+ /**
109
+ * Read the cache file, keeping only entries that still parse as facts about `accountId`.
110
+ *
111
+ * Total, including on text that is not JSON at all: a cache is a convenience, so a file somebody
112
+ * hand-edited badly costs one Jira search rather than a failed page load.
113
+ */
114
+ export const parseIssueCache = (content) => {
115
+ const parsed = Option.getOrNull(readJson(content));
116
+ if (!Predicate.isObject(parsed))
117
+ return emptyCache;
118
+ const accountId = Predicate.isString(parsed.accountId) ? parsed.accountId : null;
119
+ const cloudId = Predicate.isString(parsed.cloudId) ? parsed.cloudId : null;
120
+ const issues = new Map();
121
+ const stored = parsed.issues;
122
+ if (Predicate.isObject(stored)) {
123
+ for (const [key, entry] of Object.entries(stored)) {
124
+ if (!isTicketKey(key) || !Predicate.isObject(entry))
125
+ continue;
126
+ if (!Predicate.isNumber(entry.checkedAtMs) ||
127
+ (entry.mine !== null && !Predicate.isBoolean(entry.mine)) ||
128
+ (entry.assignment !== "mine" && entry.assignment !== "other-assignee" &&
129
+ entry.assignment !== "unassigned" && entry.assignment !== "unknown") ||
130
+ entry.mine !== ownership(entry.assignment))
131
+ continue;
132
+ issues.set(key, {
133
+ assignee: Predicate.isString(entry.assignee) ? entry.assignee : null,
134
+ assignment: entry.assignment,
135
+ checkedAtMs: entry.checkedAtMs,
136
+ key,
137
+ mine: entry.mine,
138
+ title: Predicate.isString(entry.title) ? entry.title : null
139
+ });
140
+ }
141
+ }
142
+ return { accountId, cloudId, issues };
143
+ };
144
+ export const serializeIssueCache = (cache) => JSON.stringify({
145
+ accountId: cache.accountId,
146
+ cloudId: cache.cloudId,
147
+ issues: Object.fromEntries([...cache.issues.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, fact]) => [key, {
148
+ assignee: fact.assignee,
149
+ assignment: fact.assignment,
150
+ checkedAtMs: fact.checkedAtMs,
151
+ mine: fact.mine,
152
+ title: fact.title
153
+ }]))
154
+ }, null, 2);
155
+ /** The keys a lookup has to actually ask about: never seen, or seen too long ago. */
156
+ export const staleKeys = (options) => [...new Set(options.keys)]
157
+ .filter(isTicketKey)
158
+ .filter((key) => {
159
+ const cached = options.cache.issues.get(key);
160
+ return cached === undefined || options.nowMs - cached.checkedAtMs >= options.ttlMs;
161
+ })
162
+ .sort();
163
+ /** Fold fresh answers into the cache, keeping what was not asked about this time. */
164
+ export const mergeCache = (options) => {
165
+ // A different account makes every stored `mine` a claim about somebody else.
166
+ const sameIdentity = options.cache.accountId === options.accountId && options.cache.cloudId === options.cloudId;
167
+ const queried = new Set(options.queriedKeys);
168
+ const kept = sameIdentity
169
+ ? [...options.cache.issues.entries()].filter(([key]) => !queried.has(key))
170
+ : [];
171
+ const issues = new Map(kept);
172
+ for (const [key, fact] of options.fresh)
173
+ issues.set(key, { ...fact, checkedAtMs: options.nowMs });
174
+ return { accountId: options.accountId, cloudId: options.cloudId, issues };
175
+ };
176
+ /** `key in (A-1, A-2)`, for the keys a search is about. */
177
+ export const keyClause = (keys) => `key in (${keys.join(", ")})`;
178
+ /** Split keys into searches, so one long week cannot blow the query string. */
179
+ const searchBatches = (keys) => {
180
+ const batches = [];
181
+ for (const key of keys) {
182
+ const last = batches[batches.length - 1];
183
+ if (last === undefined || last.length >= KEYS_PER_SEARCH)
184
+ batches.push([key]);
185
+ else
186
+ last.push(key);
187
+ }
188
+ return batches;
189
+ };
190
+ export const layer = Layer.effect(IssueFacts, Effect.gen(function* () {
191
+ const fs = yield* FileSystem.FileSystem;
192
+ const path = yield* Path.Path;
193
+ const home = (yield* HomeDirectory).path;
194
+ const jira = yield* JiraApiClient;
195
+ const auth = yield* JiraAuth;
196
+ const dir = path.join(home, CACHE_DIR);
197
+ const filePath = path.join(dir, CACHE_FILE);
198
+ const readCache = Effect.gen(function* () {
199
+ const exists = yield* fs.exists(filePath);
200
+ if (!exists)
201
+ return emptyCache;
202
+ const content = yield* fs.readFileString(filePath);
203
+ return yield* Effect.try({ catch: () => emptyCache, try: () => parseIssueCache(content) });
204
+ }).pipe(Effect.orElseSucceed(() => emptyCache));
205
+ const writeCache = (cache) => Effect.gen(function* () {
206
+ const exists = yield* fs.exists(dir);
207
+ if (!exists)
208
+ yield* fs.makeDirectory(dir, { recursive: true });
209
+ yield* fs.writeFileString(filePath, serializeIssueCache(cache));
210
+ }).pipe(Effect.ignore);
211
+ /** My own account id. Failure or an unrecognised answer cannot authorize cached ownership. */
212
+ const accountId = jira.getCurrentUser({}).pipe(Effect.map((self) => {
213
+ if (!isJsonObject(self))
214
+ return null;
215
+ const id = self["accountId"];
216
+ return Predicate.isString(id) ? id : null;
217
+ }), Effect.mapError(() => "unavailable"));
218
+ const search = (keys) => jira.searchIssuesUsingJql({
219
+ params: { fields: ["summary", "assignee"], jql: keyClause(keys), maxResults: keys.length }
220
+ }).pipe(Effect.map((result) => result.issues ?? []), Effect.mapError(() => "unavailable"));
221
+ return IssueFacts.of({
222
+ lookup: (keys) => Effect.gen(function* () {
223
+ const wanted = [...new Set(keys)].filter(isTicketKey);
224
+ if (wanted.length === 0)
225
+ return { checked: true, facts: new Map() };
226
+ const factsOf = (from) => new Map(wanted.flatMap((key) => {
227
+ const cached = from.issues.get(key);
228
+ return cached === undefined ? [] : [
229
+ [key, {
230
+ assignee: cached.assignee,
231
+ assignment: cached.assignment,
232
+ key,
233
+ mine: cached.mine,
234
+ title: cached.title
235
+ }]
236
+ ];
237
+ }));
238
+ // A logged-out Jira resolves an empty cloudId into a URL Atlassian answers with a 404,
239
+ // which would otherwise look exactly like "none of these issues exist" — and mark every
240
+ // ticket as somebody else's.
241
+ const loggedIn = yield* auth.isLoggedIn().pipe(Effect.orElseSucceed(() => false));
242
+ if (!loggedIn)
243
+ return { checked: false, facts: new Map() };
244
+ const mine = yield* accountId.pipe(Effect.orElseSucceed(() => null));
245
+ const cloudId = yield* auth.getCloudId().pipe(Effect.orElseSucceed(() => ""));
246
+ if (mine === null || cloudId === "")
247
+ return { checked: false, facts: new Map() };
248
+ const cache = yield* readCache;
249
+ // `mine` is account-relative. Even a fresh cache belongs to nobody until the current
250
+ // identity has been verified, and a cache for another account contributes no facts.
251
+ const scopedCache = cache.accountId === mine && cache.cloudId === cloudId ? cache : emptyCache;
252
+ const nowMs = yield* Clock.currentTimeMillis;
253
+ const stale = staleKeys({ cache: scopedCache, keys: wanted, nowMs, ttlMs: OWNERSHIP_TTL_MS });
254
+ if (stale.length === 0)
255
+ return { checked: true, facts: factsOf(scopedCache) };
256
+ const batches = yield* Effect.all(searchBatches(stale).map(search), { concurrency: 2 }).pipe(Effect.map((results) => results.flat()), Effect.orElseSucceed(() => null));
257
+ // Jira refused. What is cached still stands; what is not stays unknown.
258
+ if (batches === null)
259
+ return { checked: false, facts: factsOf(scopedCache) };
260
+ const fresh = factsFromIssues(batches, mine);
261
+ // A key asked about and not returned — deleted, or in a project this account cannot see —
262
+ // gets no entry at all. It stays unknown, so it is re-asked next time and never filtered:
263
+ // recording it as somebody else's is the one mistake that would hide real hours.
264
+ const updated = mergeCache({
265
+ accountId: mine,
266
+ cloudId,
267
+ queriedKeys: stale,
268
+ cache: scopedCache,
269
+ fresh,
270
+ nowMs
271
+ });
272
+ yield* writeCache(updated);
273
+ return { checked: true, facts: factsOf(updated) };
274
+ })
275
+ });
276
+ }));
277
+ //# sourceMappingURL=IssueFacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IssueFacts.js","sourceRoot":"","sources":["../../../src/services/IssueFacts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /** Private positive-evidence probe; absence from a feed is never deletion evidence. */
2
+ import type { JiraApiClientContract } from "@knpkv/jira-api-client";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Schema from "effect/Schema";
5
+ declare const JiraDeletionEvidenceError_base: Schema.Class<JiraDeletionEvidenceError, Schema.TaggedStruct<"JiraDeletionEvidenceError", {
6
+ readonly message: Schema.String;
7
+ }>, import("effect/Cause").YieldableError>;
8
+ export declare class JiraDeletionEvidenceError extends JiraDeletionEvidenceError_base {
9
+ }
10
+ export type JiraDeletionEvidence = {
11
+ readonly _tag: "DeletedAfterCreation";
12
+ readonly updatedTime: number;
13
+ } | {
14
+ readonly _tag: "Unproved";
15
+ };
16
+ /** Only a complete page with an exact post-creation ID is positive evidence, never release authority. */
17
+ export declare const readJiraDeletionEvidence: (client: Pick<JiraApiClientContract, "getIdsOfWorklogsDeletedSince">, entryId: string, createdAtMs: number) => Effect.Effect<JiraDeletionEvidence, JiraDeletionEvidenceError>;
18
+ export {};
19
+ //# sourceMappingURL=ProviderDeletion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProviderDeletion.d.ts","sourceRoot":"","sources":["../../../src/services/ProviderDeletion.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;;;;AAEvC,qBAAa,yBAA0B,SAAQ,8BAG9C;CAAG;AAEJ,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAA;AAWjC,yGAAyG;AACzG,eAAO,MAAM,wBAAwB,WAC3B,IAAI,CAAC,qBAAqB,EAAE,8BAA8B,CAAC,WAC1D,MAAM,eACF,MAAM,KAClB,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,yBAAyB,CAgC5D,CAAA"}
@@ -0,0 +1,37 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ export class JiraDeletionEvidenceError extends Schema.TaggedError()("JiraDeletionEvidenceError", { message: Schema.String }) {
4
+ }
5
+ const safeTime = (value) => value !== undefined && Number.isSafeInteger(value) && value >= 0;
6
+ const numericEntryId = (entryId) => {
7
+ if (!/^[1-9]\d*$/u.test(entryId))
8
+ return null;
9
+ const value = Number(entryId);
10
+ return Number.isSafeInteger(value) ? value : null;
11
+ };
12
+ /** Only a complete page with an exact post-creation ID is positive evidence, never release authority. */
13
+ export const readJiraDeletionEvidence = (client, entryId, createdAtMs) => Effect.gen(function* () {
14
+ const id = numericEntryId(entryId);
15
+ if (id === null || !safeTime(createdAtMs)) {
16
+ return yield* new JiraDeletionEvidenceError({ message: "Jira binding lacks a safe provider checkpoint or ID" });
17
+ }
18
+ const page = yield* client.getIdsOfWorklogsDeletedSince({ params: { since: createdAtMs } }).pipe(Effect.mapError(() => new JiraDeletionEvidenceError({ message: "Jira deletion feed could not be verified" })));
19
+ if (!safeTime(page.since) || !safeTime(page.until) || page.until < page.since ||
20
+ page.lastPage === undefined || page.values === undefined ||
21
+ page.lastPage === false && (page.nextPage === undefined || page.nextPage === "")) {
22
+ return yield* new JiraDeletionEvidenceError({ message: "Jira deletion page is incomplete" });
23
+ }
24
+ let previous = page.since;
25
+ for (const row of page.values) {
26
+ if (!safeTime(row.worklogId) || row.worklogId <= 0 || !safeTime(row.updatedTime) ||
27
+ row.updatedTime < previous || row.updatedTime > page.until) {
28
+ return yield* new JiraDeletionEvidenceError({ message: "Jira deletion page has invalid ordering or identity" });
29
+ }
30
+ previous = row.updatedTime;
31
+ }
32
+ const matchedTime = page.values.find((row) => row.worklogId === id && row.updatedTime !== undefined && row.updatedTime > createdAtMs)?.updatedTime;
33
+ return matchedTime === undefined
34
+ ? { _tag: "Unproved" }
35
+ : { _tag: "DeletedAfterCreation", updatedTime: matchedTime };
36
+ });
37
+ //# sourceMappingURL=ProviderDeletion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProviderDeletion.js","sourceRoot":"","sources":["../../../src/services/ProviderDeletion.ts"],"names":[],"mappings":"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"}