@knpkv/jira-clockify 1.3.0 → 1.4.1
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.
- package/README.md +245 -1
- package/dist/package.json +36 -12
- package/dist/src/agent/agentSettings.d.ts +18 -0
- package/dist/src/agent/agentSettings.d.ts.map +1 -0
- package/dist/src/agent/agentSettings.js +23 -0
- package/dist/src/agent/agentSettings.js.map +1 -0
- package/dist/src/agent/schedule.d.ts +20 -0
- package/dist/src/agent/schedule.d.ts.map +1 -0
- package/dist/src/agent/schedule.js +189 -0
- package/dist/src/agent/schedule.js.map +1 -0
- package/dist/src/agent/sessions.d.ts +427 -0
- package/dist/src/agent/sessions.d.ts.map +1 -0
- package/dist/src/agent/sessions.js +868 -0
- package/dist/src/agent/sessions.js.map +1 -0
- package/dist/src/agent/sourceConsumption.d.ts +79 -0
- package/dist/src/agent/sourceConsumption.d.ts.map +1 -0
- package/dist/src/agent/sourceConsumption.js +191 -0
- package/dist/src/agent/sourceConsumption.js.map +1 -0
- package/dist/src/agent/watch.d.ts +76 -0
- package/dist/src/agent/watch.d.ts.map +1 -0
- package/dist/src/agent/watch.js +45 -0
- package/dist/src/agent/watch.js.map +1 -0
- package/dist/src/agent/writePlanning.d.ts +154 -0
- package/dist/src/agent/writePlanning.d.ts.map +1 -0
- package/dist/src/agent/writePlanning.js +210 -0
- package/dist/src/agent/writePlanning.js.map +1 -0
- package/dist/src/bin.d.ts +3 -0
- package/dist/src/bin.d.ts.map +1 -0
- package/dist/src/bin.js +9 -3
- package/dist/src/bin.js.map +1 -1
- package/dist/src/cli/agentWrite.d.ts +164 -0
- package/dist/src/cli/agentWrite.d.ts.map +1 -0
- package/dist/src/cli/agentWrite.js +362 -0
- package/dist/src/cli/agentWrite.js.map +1 -0
- package/dist/src/cli/auth.d.ts +8 -0
- package/dist/src/cli/auth.d.ts.map +1 -0
- package/dist/src/cli/auth.js +10 -10
- package/dist/src/cli/auth.js.map +1 -1
- package/dist/src/cli/calendar.d.ts +42 -0
- package/dist/src/cli/calendar.d.ts.map +1 -0
- package/dist/src/cli/calendar.js +189 -0
- package/dist/src/cli/calendar.js.map +1 -0
- package/dist/src/cli/config.d.ts +12 -0
- package/dist/src/cli/config.d.ts.map +1 -0
- package/dist/src/cli/config.js +163 -21
- package/dist/src/cli/config.js.map +1 -1
- package/dist/src/cli/fetchTicket.d.ts +51 -0
- package/dist/src/cli/fetchTicket.d.ts.map +1 -0
- package/dist/src/cli/fetchTicket.js +18 -2
- package/dist/src/cli/fetchTicket.js.map +1 -1
- package/dist/src/cli/fuzzySelect.d.ts +24 -0
- package/dist/src/cli/fuzzySelect.d.ts.map +1 -0
- package/dist/src/cli/layers.d.ts +40 -0
- package/dist/src/cli/layers.d.ts.map +1 -0
- package/dist/src/cli/layers.js +57 -13
- package/dist/src/cli/layers.js.map +1 -1
- package/dist/src/cli/list.d.ts +8 -0
- package/dist/src/cli/list.d.ts.map +1 -0
- package/dist/src/cli/list.js +2 -2
- package/dist/src/cli/list.js.map +1 -1
- package/dist/src/cli/reconcile.d.ts +129 -0
- package/dist/src/cli/reconcile.d.ts.map +1 -0
- package/dist/src/cli/reconcile.js +635 -46
- package/dist/src/cli/reconcile.js.map +1 -1
- package/dist/src/cli/root.d.ts +15 -0
- package/dist/src/cli/root.d.ts.map +1 -0
- package/dist/src/cli/root.js +4 -3
- package/dist/src/cli/root.js.map +1 -1
- package/dist/src/cli/runtimeFailure.d.ts +10 -0
- package/dist/src/cli/runtimeFailure.d.ts.map +1 -0
- package/dist/src/cli/runtimeFailure.js +17 -0
- package/dist/src/cli/runtimeFailure.js.map +1 -0
- package/dist/src/cli/setup.d.ts +10 -0
- package/dist/src/cli/setup.d.ts.map +1 -0
- package/dist/src/cli/setup.js +7 -7
- package/dist/src/cli/setup.js.map +1 -1
- package/dist/src/cli/timer/discard.d.ts +4 -0
- package/dist/src/cli/timer/discard.d.ts.map +1 -0
- package/dist/src/cli/timer/discard.js +2 -2
- package/dist/src/cli/timer/discard.js.map +1 -1
- package/dist/src/cli/timer/edit.d.ts +22 -0
- package/dist/src/cli/timer/edit.d.ts.map +1 -0
- package/dist/src/cli/timer/edit.js +62 -42
- package/dist/src/cli/timer/edit.js.map +1 -1
- package/dist/src/cli/timer/index.d.ts +12 -0
- package/dist/src/cli/timer/index.d.ts.map +1 -0
- package/dist/src/cli/timer/log.d.ts +16 -0
- package/dist/src/cli/timer/log.d.ts.map +1 -0
- package/dist/src/cli/timer/log.js +6 -6
- package/dist/src/cli/timer/log.js.map +1 -1
- package/dist/src/cli/timer/start.d.ts +21 -0
- package/dist/src/cli/timer/start.d.ts.map +1 -0
- package/dist/src/cli/timer/start.js +9 -9
- package/dist/src/cli/timer/start.js.map +1 -1
- package/dist/src/cli/timer/status.d.ts +22 -0
- package/dist/src/cli/timer/status.d.ts.map +1 -0
- package/dist/src/cli/timer/status.js +3 -3
- package/dist/src/cli/timer/status.js.map +1 -1
- package/dist/src/cli/timer/stop.d.ts +48 -0
- package/dist/src/cli/timer/stop.d.ts.map +1 -0
- package/dist/src/cli/timer/stop.js +19 -19
- package/dist/src/cli/timer/stop.js.map +1 -1
- package/dist/src/cli/timer.d.ts +11 -0
- package/dist/src/cli/timer.d.ts.map +1 -0
- package/dist/src/cli/timer.js +1 -1
- package/dist/src/cli/timer.js.map +1 -1
- package/dist/src/cli/watch.d.ts +54 -0
- package/dist/src/cli/watch.d.ts.map +1 -0
- package/dist/src/cli/watch.js +412 -0
- package/dist/src/cli/watch.js.map +1 -0
- package/dist/src/cli/watchLease.d.ts +99 -0
- package/dist/src/cli/watchLease.d.ts.map +1 -0
- package/dist/src/cli/watchLease.js +204 -0
- package/dist/src/cli/watchLease.js.map +1 -0
- package/dist/src/cli/writerGuard.d.ts +37 -0
- package/dist/src/cli/writerGuard.d.ts.map +1 -0
- package/dist/src/cli/writerGuard.js +92 -0
- package/dist/src/cli/writerGuard.js.map +1 -0
- package/dist/src/index.d.ts +46 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +46 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/main.d.ts +4 -0
- package/dist/src/main.d.ts.map +1 -0
- package/dist/src/services/AgentSessionReader.d.ts +121 -0
- package/dist/src/services/AgentSessionReader.d.ts.map +1 -0
- package/dist/src/services/AgentSessionReader.js +392 -0
- package/dist/src/services/AgentSessionReader.js.map +1 -0
- package/dist/src/services/ClockifyAuth.d.ts +47 -0
- package/dist/src/services/ClockifyAuth.d.ts.map +1 -0
- package/dist/src/services/ClockifyAuth.js +2 -2
- package/dist/src/services/ClockifyAuth.js.map +1 -1
- package/dist/src/services/CodexTranscript.d.ts +112 -0
- package/dist/src/services/CodexTranscript.d.ts.map +1 -0
- package/dist/src/services/CodexTranscript.js +96 -0
- package/dist/src/services/CodexTranscript.js.map +1 -0
- package/dist/src/services/ConfigService.d.ts +99 -0
- package/dist/src/services/ConfigService.d.ts.map +1 -0
- package/dist/src/services/ConfigService.js +79 -7
- package/dist/src/services/ConfigService.js.map +1 -1
- package/dist/src/services/HomeDirectory.d.ts +17 -0
- package/dist/src/services/HomeDirectory.d.ts.map +1 -0
- package/dist/src/services/HomeDirectory.js +1 -1
- package/dist/src/services/IssueFacts.d.ts +127 -0
- package/dist/src/services/IssueFacts.d.ts.map +1 -0
- package/dist/src/services/IssueFacts.js +277 -0
- package/dist/src/services/IssueFacts.js.map +1 -0
- package/dist/src/services/ProviderDeletion.d.ts +19 -0
- package/dist/src/services/ProviderDeletion.d.ts.map +1 -0
- package/dist/src/services/ProviderDeletion.js +37 -0
- package/dist/src/services/ProviderDeletion.js.map +1 -0
- package/dist/src/services/ReconcileService.d.ts +378 -0
- package/dist/src/services/ReconcileService.d.ts.map +1 -0
- package/dist/src/services/ReconcileService.js +1270 -79
- package/dist/src/services/ReconcileService.js.map +1 -1
- package/dist/src/services/SavedEntries.d.ts +40 -0
- package/dist/src/services/SavedEntries.d.ts.map +1 -0
- package/dist/src/services/SavedEntries.js +209 -0
- package/dist/src/services/SavedEntries.js.map +1 -0
- package/dist/src/services/SessionAttributor.d.ts +114 -0
- package/dist/src/services/SessionAttributor.d.ts.map +1 -0
- package/dist/src/services/SessionAttributor.js +240 -0
- package/dist/src/services/SessionAttributor.js.map +1 -0
- package/dist/src/services/SourceLedger.d.ts +104 -0
- package/dist/src/services/SourceLedger.d.ts.map +1 -0
- package/dist/src/services/SourceLedger.js +302 -0
- package/dist/src/services/SourceLedger.js.map +1 -0
- package/dist/src/services/StateWriter.d.ts +39 -0
- package/dist/src/services/StateWriter.d.ts.map +1 -0
- package/dist/src/services/TicketService.d.ts +63 -0
- package/dist/src/services/TicketService.d.ts.map +1 -0
- package/dist/src/services/TimerService.d.ts +145 -0
- package/dist/src/services/TimerService.d.ts.map +1 -0
- package/dist/src/services/TimerService.js +113 -80
- package/dist/src/services/TimerService.js.map +1 -1
- package/dist/src/services/internal/JiraWorklogPost.d.ts +7 -0
- package/dist/src/services/internal/JiraWorklogPost.d.ts.map +1 -0
- package/dist/src/services/internal/JiraWorklogPost.js +32 -0
- package/dist/src/services/internal/JiraWorklogPost.js.map +1 -0
- package/dist/src/testing/fakeHeadless.d.ts +318 -0
- package/dist/src/testing/fakeHeadless.d.ts.map +1 -0
- package/dist/src/testing/fakeHeadless.js +1149 -0
- package/dist/src/testing/fakeHeadless.js.map +1 -0
- package/dist/src/tui/App.d.ts +6 -0
- package/dist/src/tui/App.d.ts.map +1 -0
- package/dist/src/tui/App.js +1 -1
- package/dist/src/tui/App.js.map +1 -1
- package/dist/src/tui/atoms/runtime.d.ts +8 -0
- package/dist/src/tui/atoms/runtime.d.ts.map +1 -0
- package/dist/src/tui/atoms/runtime.js +1 -1
- package/dist/src/tui/atoms/runtime.js.map +1 -1
- package/dist/src/tui/atoms/tickets.d.ts +3 -0
- package/dist/src/tui/atoms/tickets.d.ts.map +1 -0
- package/dist/src/tui/atoms/timer.d.ts +11 -0
- package/dist/src/tui/atoms/timer.d.ts.map +1 -0
- package/dist/src/tui/atoms/timer.js +1 -1
- package/dist/src/tui/atoms/timer.js.map +1 -1
- package/dist/src/tui/atoms/ui.d.ts +11 -0
- package/dist/src/tui/atoms/ui.d.ts.map +1 -0
- package/dist/src/tui/atoms/ui.js +1 -1
- package/dist/src/tui/atoms/ui.js.map +1 -1
- package/dist/src/tui/components/BigTimer.d.ts +2 -0
- package/dist/src/tui/components/BigTimer.d.ts.map +1 -0
- package/dist/src/tui/components/Footer.d.ts +2 -0
- package/dist/src/tui/components/Footer.d.ts.map +1 -0
- package/dist/src/tui/components/Header.d.ts +2 -0
- package/dist/src/tui/components/Header.d.ts.map +1 -0
- package/dist/src/tui/components/Header.js +1 -1
- package/dist/src/tui/components/Header.js.map +1 -1
- package/dist/src/tui/components/PopupInput.d.ts +18 -0
- package/dist/src/tui/components/PopupInput.d.ts.map +1 -0
- package/dist/src/tui/components/PopupMessage.d.ts +20 -0
- package/dist/src/tui/components/PopupMessage.d.ts.map +1 -0
- package/dist/src/tui/components/TicketList.d.ts +2 -0
- package/dist/src/tui/components/TicketList.d.ts.map +1 -0
- package/dist/src/tui/components/TicketList.js +1 -1
- package/dist/src/tui/components/TicketList.js.map +1 -1
- package/dist/src/tui/components/TicketRow.d.ts +8 -0
- package/dist/src/tui/components/TicketRow.d.ts.map +1 -0
- package/dist/src/tui/components/TimerDisplay.d.ts +2 -0
- package/dist/src/tui/components/TimerDisplay.d.ts.map +1 -0
- package/dist/src/tui/components/index.d.ts +12 -0
- package/dist/src/tui/components/index.d.ts.map +1 -0
- package/dist/src/tui/context/theme.d.ts +15 -0
- package/dist/src/tui/context/theme.d.ts.map +1 -0
- package/dist/src/tui/hooks/useElapsedTimer.d.ts +6 -0
- package/dist/src/tui/hooks/useElapsedTimer.d.ts.map +1 -0
- package/dist/src/tui/hooks/useElapsedTimer.js +1 -1
- package/dist/src/tui/hooks/useElapsedTimer.js.map +1 -1
- package/dist/src/tui/hooks/useTerminalSize.d.ts +5 -0
- package/dist/src/tui/hooks/useTerminalSize.d.ts.map +1 -0
- package/dist/src/utils/hints.d.ts +12 -0
- package/dist/src/utils/hints.d.ts.map +1 -0
- package/dist/src/utils/hints.js +12 -0
- package/dist/src/utils/hints.js.map +1 -0
- package/dist/src/utils/time.d.ts +104 -0
- package/dist/src/utils/time.d.ts.map +1 -0
- package/dist/src/utils/time.js +74 -8
- package/dist/src/utils/time.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +36 -18
- 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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"}
|