workday-daemon 0.17.2 → 0.19.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 (43) hide show
  1. package/README.md +3 -0
  2. package/dist/cli.js +196 -39
  3. package/dist/cli.js.map +1 -1
  4. package/dist/core/constants.d.ts +7 -1
  5. package/dist/core/constants.js +11 -1
  6. package/dist/core/constants.js.map +1 -1
  7. package/dist/core/day-edit.d.ts +41 -0
  8. package/dist/core/day-edit.js +75 -0
  9. package/dist/core/day-edit.js.map +1 -0
  10. package/dist/core/session-tracker.d.ts +6 -0
  11. package/dist/core/session-tracker.js +9 -0
  12. package/dist/core/session-tracker.js.map +1 -1
  13. package/dist/core/types.d.ts +97 -1
  14. package/dist/core/types.js +11 -0
  15. package/dist/core/types.js.map +1 -1
  16. package/dist/http-server.d.ts +20 -0
  17. package/dist/http-server.js +266 -45
  18. package/dist/http-server.js.map +1 -1
  19. package/dist/push/month-report.d.ts +18 -0
  20. package/dist/push/month-report.js +132 -0
  21. package/dist/push/month-report.js.map +1 -0
  22. package/dist/push/push-log.d.ts +15 -0
  23. package/dist/push/push-log.js +70 -0
  24. package/dist/push/push-log.js.map +1 -0
  25. package/dist/push/reconcile.d.ts +21 -0
  26. package/dist/push/reconcile.js +292 -0
  27. package/dist/push/reconcile.js.map +1 -0
  28. package/dist/push/tempo-approvals.d.ts +12 -0
  29. package/dist/push/tempo-approvals.js +104 -0
  30. package/dist/push/tempo-approvals.js.map +1 -0
  31. package/dist/push/tempo-client.d.ts +63 -0
  32. package/dist/push/tempo-client.js +46 -10
  33. package/dist/push/tempo-client.js.map +1 -1
  34. package/dist/push/tempo-pusher.d.ts +4 -4
  35. package/dist/push/tempo-pusher.js +88 -162
  36. package/dist/push/tempo-pusher.js.map +1 -1
  37. package/dist/push/tempo-schedule.d.ts +10 -0
  38. package/dist/push/tempo-schedule.js +81 -0
  39. package/dist/push/tempo-schedule.js.map +1 -0
  40. package/dist/push/tempo-snapshot.d.ts +18 -0
  41. package/dist/push/tempo-snapshot.js +98 -0
  42. package/dist/push/tempo-snapshot.js.map +1 -0
  43. package/package.json +2 -2
@@ -0,0 +1,132 @@
1
+ import { readDailyLog, computeTotalClaimedMs } from '../core/daily-log.js';
2
+ import { DayStatus } from '../core/types.js';
3
+ import { MonthDayStatus } from '../core/types.js';
4
+ import { buildReport } from './report-builder.js';
5
+ import { loadPushLog, loadTombstones } from './push-log.js';
6
+ import { loadMonthSnapshot } from './tempo-snapshot.js';
7
+ import { computeDayDrift } from './reconcile.js';
8
+ /** First/last calendar day of a month as YYYY-MM-DD. */
9
+ export function getMonthRange(year, month) {
10
+ const mm = String(month).padStart(2, '0');
11
+ const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
12
+ return {
13
+ from: `${year}-${mm}-01`,
14
+ to: `${year}-${mm}-${String(lastDay).padStart(2, '0')}`,
15
+ };
16
+ }
17
+ /** Parse "YYYY-MM" → {year, month} or null when malformed/out of range. */
18
+ export function parseYearMonth(value) {
19
+ const match = value.match(/^(\d{4})-(\d{2})$/);
20
+ if (!match)
21
+ return null;
22
+ const year = parseInt(match[1], 10);
23
+ const month = parseInt(match[2], 10);
24
+ if (month < 1 || month > 12)
25
+ return null;
26
+ return { year, month };
27
+ }
28
+ // Flag-based fallback (no Tempo snapshot): sync state from the daily log
29
+ // alone. Confirmed is dead but old files may still carry it — anything not
30
+ // pushed counts as draft.
31
+ function deriveDayStatus(log) {
32
+ if (!log)
33
+ return MonthDayStatus.None;
34
+ if (log.status === DayStatus.Pushed)
35
+ return MonthDayStatus.Pushed;
36
+ return log.pushedAt ? MonthDayStatus.Outdated : MonthDayStatus.Pending;
37
+ }
38
+ // Diff-based status: parity with the snapshot IS pushed, drift on a pushed
39
+ // day IS outdated — regardless of what the local flag says. Self-heals both
40
+ // stuck-Outdated (edit reverted → parity) and false-Pushed (remote edits).
41
+ function deriveDayStatusFromDrift(log, drift) {
42
+ if (drift.length === 0)
43
+ return log.pushedAt ? MonthDayStatus.Pushed : MonthDayStatus.Pending;
44
+ return log.pushedAt ? MonthDayStatus.Outdated : MonthDayStatus.Pending;
45
+ }
46
+ function toMonthDayTask(entry) {
47
+ return {
48
+ task: entry.task,
49
+ seconds: entry.totalSeconds,
50
+ kind: entry.kind,
51
+ sessionCount: entry.sessionCount,
52
+ ...(entry.description !== undefined ? { description: entry.description } : {}),
53
+ ...(entry.activity !== undefined ? { activity: entry.activity } : {}),
54
+ };
55
+ }
56
+ /**
57
+ * Month aggregate for the timesheets tab: daily logs + the push view of each
58
+ * day (rounded task lines, session-born entries folded). Pure disk read — no
59
+ * Tempo calls, works offline. The caller is responsible for flushing today's
60
+ * live log to disk first when the month includes today.
61
+ */
62
+ export function buildMonthResponse(year, month, config) {
63
+ const { from, to } = getMonthRange(year, month);
64
+ const tasksByDate = new Map();
65
+ const entriesByDate = new Map();
66
+ for (const entry of buildReport(from, to, config)) {
67
+ const list = tasksByDate.get(entry.date) ?? [];
68
+ list.push(toMonthDayTask(entry));
69
+ tasksByDate.set(entry.date, list);
70
+ const raw = entriesByDate.get(entry.date) ?? [];
71
+ raw.push(entry);
72
+ entriesByDate.set(entry.date, raw);
73
+ }
74
+ // With a Tempo snapshot on disk the statuses come from the actual diff;
75
+ // without one they fall back to the local pushed-flags.
76
+ const snapshot = loadMonthSnapshot(year, month);
77
+ const snapById = snapshot
78
+ ? new Map(snapshot.worklogs.map(w => [w.tempoWorklogId, w]))
79
+ : null;
80
+ const pushLog = snapshot ? loadPushLog() : {};
81
+ const tombstones = snapshot ? loadTombstones() : [];
82
+ const days = [];
83
+ const totals = {
84
+ claimedMs: 0,
85
+ reportedSeconds: 0,
86
+ daysWithData: 0,
87
+ pendingDays: 0,
88
+ outdatedDays: 0,
89
+ pushedDays: 0,
90
+ };
91
+ let lastPushAt = null;
92
+ const lastDay = parseInt(to.slice(8), 10);
93
+ for (let dayNum = 1; dayNum <= lastDay; dayNum++) {
94
+ const date = `${from.slice(0, 8)}${String(dayNum).padStart(2, '0')}`;
95
+ const log = readDailyLog(date);
96
+ const tasks = tasksByDate.get(date) ?? [];
97
+ const claimedMs = log ? computeTotalClaimedMs(log) : 0;
98
+ const reportedSeconds = tasks.reduce((sum, t) => sum + t.seconds, 0);
99
+ const drift = snapById && log
100
+ ? computeDayDrift(date, entriesByDate.get(date) ?? [], pushLog, snapById, tombstones)
101
+ : null;
102
+ const status = drift !== null
103
+ ? deriveDayStatusFromDrift(log, drift)
104
+ : deriveDayStatus(log);
105
+ days.push({
106
+ date,
107
+ dayType: log?.dayType ?? null,
108
+ status,
109
+ claimedMs,
110
+ reportedSeconds,
111
+ taskCount: new Set(tasks.map(t => t.task)).size,
112
+ tasks,
113
+ pushedAt: log?.pushedAt ?? null,
114
+ ...(drift !== null ? { drift } : {}),
115
+ });
116
+ totals.claimedMs += claimedMs;
117
+ totals.reportedSeconds += reportedSeconds;
118
+ if (log)
119
+ totals.daysWithData++;
120
+ if (status === MonthDayStatus.Pending)
121
+ totals.pendingDays++;
122
+ if (status === MonthDayStatus.Outdated)
123
+ totals.outdatedDays++;
124
+ if (status === MonthDayStatus.Pushed)
125
+ totals.pushedDays++;
126
+ if (log?.pushedAt && (!lastPushAt || log.pushedAt > lastPushAt)) {
127
+ lastPushAt = log.pushedAt;
128
+ }
129
+ }
130
+ return { year, month, from, to, days, totals, lastPushAt, syncedAt: snapshot?.fetchedAt ?? null };
131
+ }
132
+ //# sourceMappingURL=month-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"month-report.js","sourceRoot":"","sources":["../../src/push/month-report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { PushLogEntry, PushTombstone } from '../core/types.js';
2
+ /** Ownership key: session aggregate `date|task`, manual entry `date|task|m:id`. */
3
+ export declare function pushLogKey(date: string, task: string, entryId?: string): string;
4
+ export declare function loadPushLog(): Record<string, PushLogEntry>;
5
+ export declare function savePushLog(log: Record<string, PushLogEntry>): void;
6
+ export declare function loadTombstones(): PushTombstone[];
7
+ export declare function saveTombstones(tombstones: readonly PushTombstone[]): void;
8
+ /** Drop tombstones whose worklogs are gone from Tempo (deleted by our push or remotely). */
9
+ export declare function removeTombstonesByWorklogIds(ids: ReadonlySet<number>): void;
10
+ /**
11
+ * Manual entry deleted locally after being pushed: drop its ownership key and
12
+ * remember the worklog as a tombstone. No-op (false) for entries that never
13
+ * had their own worklog — unpushed or session-born ones.
14
+ */
15
+ export declare function recordEntryDeletion(date: string, task: string, entryId: string): boolean;
@@ -0,0 +1,70 @@
1
+ // Push-log persistence: the ownership map localKey → what we last sent to
2
+ // Tempo, plus tombstones for pushed-then-deleted manual entries. Tombstones
3
+ // are only recorded here; delete propagation to Tempo lands with the
4
+ // reconcile engine (mirror-sync stage 3).
5
+ import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { getDataDir } from '../core/config.js';
8
+ import { PUSH_LOG_FILE, PUSH_TOMBSTONES_FILE, TMP_EXTENSION } from '../core/constants.js';
9
+ function writeJsonAtomic(filePath, value) {
10
+ mkdirSync(getDataDir(), { recursive: true });
11
+ const tmpPath = filePath + TMP_EXTENSION;
12
+ writeFileSync(tmpPath, JSON.stringify(value, null, 2), 'utf-8');
13
+ renameSync(tmpPath, filePath);
14
+ }
15
+ function readJsonOr(filePath, fallback) {
16
+ if (!existsSync(filePath))
17
+ return fallback;
18
+ try {
19
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
20
+ }
21
+ catch {
22
+ return fallback;
23
+ }
24
+ }
25
+ /** Ownership key: session aggregate `date|task`, manual entry `date|task|m:id`. */
26
+ export function pushLogKey(date, task, entryId) {
27
+ return entryId ? `${date}|${task}|m:${entryId}` : `${date}|${task}`;
28
+ }
29
+ export function loadPushLog() {
30
+ return readJsonOr(join(getDataDir(), PUSH_LOG_FILE), {});
31
+ }
32
+ export function savePushLog(log) {
33
+ writeJsonAtomic(join(getDataDir(), PUSH_LOG_FILE), log);
34
+ }
35
+ export function loadTombstones() {
36
+ return readJsonOr(join(getDataDir(), PUSH_TOMBSTONES_FILE), []);
37
+ }
38
+ export function saveTombstones(tombstones) {
39
+ writeJsonAtomic(join(getDataDir(), PUSH_TOMBSTONES_FILE), tombstones);
40
+ }
41
+ /** Drop tombstones whose worklogs are gone from Tempo (deleted by our push or remotely). */
42
+ export function removeTombstonesByWorklogIds(ids) {
43
+ const rest = loadTombstones().filter(t => !ids.has(t.tempoWorklogId));
44
+ saveTombstones(rest);
45
+ }
46
+ /**
47
+ * Manual entry deleted locally after being pushed: drop its ownership key and
48
+ * remember the worklog as a tombstone. No-op (false) for entries that never
49
+ * had their own worklog — unpushed or session-born ones.
50
+ */
51
+ export function recordEntryDeletion(date, task, entryId) {
52
+ const log = loadPushLog();
53
+ const key = pushLogKey(date, task, entryId);
54
+ const owned = log[key];
55
+ if (!owned)
56
+ return false;
57
+ delete log[key];
58
+ savePushLog(log);
59
+ const tombstones = loadTombstones();
60
+ tombstones.push({
61
+ date,
62
+ task,
63
+ entryId,
64
+ tempoWorklogId: owned.tempoWorklogId,
65
+ deletedAt: new Date().toISOString(),
66
+ });
67
+ saveTombstones(tombstones);
68
+ return true;
69
+ }
70
+ //# sourceMappingURL=push-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-log.js","sourceRoot":"","sources":["../../src/push/push-log.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { JiraIssue, PushLogEntry, PushPlanEntry, PushTombstone, TaskDayReport, TempoWorklog } from '../core/types.js';
2
+ export declare function formatHours(seconds: number): string;
3
+ export interface PushPlanOptions {
4
+ readonly tombstones?: readonly PushTombstone[];
5
+ readonly from?: string;
6
+ readonly to?: string;
7
+ readonly datesWithData?: ReadonlySet<string>;
8
+ }
9
+ /**
10
+ * Build the push plan: desired report vs Tempo snapshot, ownership from
11
+ * push-log. Orphaned desired entries re-adopt unowned worklogs on the same
12
+ * (date, issue) — by exact content for manual entries, by being the only
13
+ * candidate for session aggregates — instead of blindly creating duplicates.
14
+ */
15
+ export declare function buildPushPlan(report: readonly TaskDayReport[], jiraMap: Map<string, JiraIssue>, pushLog: Record<string, PushLogEntry>, tempoWorklogs: readonly TempoWorklog[], options?: PushPlanOptions): PushPlanEntry[];
16
+ /**
17
+ * Offline drift check for one day — the status side of the same diff. Works
18
+ * without Jira (ownership keys carry date|task), against a month snapshot.
19
+ * Returns human-readable drift lines; empty array = verified parity.
20
+ */
21
+ export declare function computeDayDrift(date: string, entries: readonly TaskDayReport[], pushLog: Record<string, PushLogEntry>, snapById: ReadonlyMap<number, TempoWorklog>, tombstones?: readonly PushTombstone[]): string[];
@@ -0,0 +1,292 @@
1
+ // Mirror-sync diff engine. Desired state (the local report) is compared
2
+ // against the ACTUAL Tempo state (worklog snapshot); push-log serves only as
3
+ // the ownership map localKey → tempoWorklogId, never as a source of truth
4
+ // about worklog content. Matching is identity-based (tempoWorklogId): field
5
+ // edits — time, text, activity, even a day move — keep the id on the Tempo
6
+ // side (verified live 2026-07-07); only a ticket move recreates the worklog.
7
+ import { TEMPO_TOLERANCE_SECONDS } from '../core/constants.js';
8
+ import { pushLogKey } from './push-log.js';
9
+ export function formatHours(seconds) {
10
+ const hours = seconds / 3600;
11
+ const rounded1 = parseFloat(hours.toFixed(1));
12
+ if (Math.abs(hours - rounded1) < 0.01)
13
+ return `${hours.toFixed(1)}h`;
14
+ return `${hours.toFixed(2)}h`;
15
+ }
16
+ function timeDrifts(a, b) {
17
+ return Math.abs(a - b) > TEMPO_TOLERANCE_SECONDS;
18
+ }
19
+ function manualTextDrifts(entry, wl) {
20
+ return (wl.description ?? '') !== (entry.description ?? '')
21
+ || (wl.activity ?? '') !== (entry.activity ?? '');
22
+ }
23
+ /** The worklog no longer matches what we sent — someone edited it in Tempo. */
24
+ function remoteChanged(own, wl, entryDate, kind) {
25
+ if (own.timeSpentSeconds !== wl.timeSpentSeconds)
26
+ return true;
27
+ if (wl.startDate !== entryDate)
28
+ return true;
29
+ if (kind === 'manual') {
30
+ return (own.description ?? '') !== (wl.description ?? '')
31
+ || (own.activity ?? '') !== (wl.activity ?? '');
32
+ }
33
+ return false;
34
+ }
35
+ /** Worklog content equals a manual entry (adoption criterion). */
36
+ function contentMatches(entry, wl) {
37
+ return !timeDrifts(wl.timeSpentSeconds, entry.totalSeconds) && !manualTextDrifts(entry, wl);
38
+ }
39
+ /**
40
+ * Build the push plan: desired report vs Tempo snapshot, ownership from
41
+ * push-log. Orphaned desired entries re-adopt unowned worklogs on the same
42
+ * (date, issue) — by exact content for manual entries, by being the only
43
+ * candidate for session aggregates — instead of blindly creating duplicates.
44
+ */
45
+ export function buildPushPlan(report, jiraMap, pushLog, tempoWorklogs, options = {}) {
46
+ const plan = [];
47
+ const tombstones = options.tombstones ?? [];
48
+ const snapById = new Map();
49
+ const byDateIssue = new Map();
50
+ for (const wl of tempoWorklogs) {
51
+ snapById.set(wl.tempoWorklogId, wl);
52
+ const key = `${wl.startDate}|${wl.issueId}`;
53
+ const list = byDateIssue.get(key) ?? [];
54
+ list.push(wl);
55
+ byDateIssue.set(key, list);
56
+ }
57
+ // Every worklog id claimed by ANY ownership key — never an adoption candidate.
58
+ const ownedIds = new Set();
59
+ for (const logEntry of Object.values(pushLog))
60
+ ownedIds.add(logEntry.tempoWorklogId);
61
+ const accounted = new Set();
62
+ const pending = new Map();
63
+ // ── Pass 1: desired entries with a live owned worklog → field diff ──
64
+ for (const entry of report) {
65
+ const jira = jiraMap.get(entry.task);
66
+ if (!jira) {
67
+ plan.push({
68
+ date: entry.date, task: entry.task, targetSeconds: entry.totalSeconds,
69
+ action: 'error', detail: 'Unresolved in Jira',
70
+ kind: entry.kind, entryId: entry.entryId, description: entry.description, activity: entry.activity,
71
+ });
72
+ continue;
73
+ }
74
+ const base = {
75
+ date: entry.date, task: entry.task, targetSeconds: entry.totalSeconds,
76
+ issueId: jira.issueId, kind: entry.kind, entryId: entry.entryId,
77
+ };
78
+ const key = pushLogKey(entry.date, entry.task, entry.kind === 'manual' ? entry.entryId : undefined);
79
+ const own = pushLog[key];
80
+ const wl = own ? snapById.get(own.tempoWorklogId) : undefined;
81
+ if (own && wl) {
82
+ accounted.add(wl.tempoWorklogId);
83
+ if (wl.issueId !== jira.issueId) {
84
+ // Same id on a different issue — identity is confused; never guess.
85
+ plan.push({ ...base, action: 'error', existingWorklogId: wl.tempoWorklogId, detail: `Worklog #${wl.tempoWorklogId} sits on another issue in Tempo — resolve manually` });
86
+ continue;
87
+ }
88
+ const moved = wl.startDate !== entry.date;
89
+ const time = timeDrifts(wl.timeSpentSeconds, entry.totalSeconds);
90
+ const text = entry.kind === 'manual' && manualTextDrifts(entry, wl);
91
+ if (!moved && !time && !text) {
92
+ plan.push({ ...base, action: 'skip', detail: `In sync (${formatHours(wl.timeSpentSeconds)})`, existingWorklogId: wl.tempoWorklogId, description: entry.description, activity: entry.activity });
93
+ continue;
94
+ }
95
+ const parts = [];
96
+ if (time)
97
+ parts.push(`${formatHours(wl.timeSpentSeconds)} → ${formatHours(entry.totalSeconds)}`);
98
+ if (text)
99
+ parts.push('text/activity changed');
100
+ if (moved)
101
+ parts.push(`moved to ${wl.startDate} in Tempo → restoring ${entry.date}`);
102
+ plan.push({
103
+ ...base,
104
+ action: 'update',
105
+ detail: parts.join('; '),
106
+ existingWorklogId: wl.tempoWorklogId,
107
+ // Session worklogs: text/activity are Tempo-side cosmetics we do not
108
+ // manage — carry the current remote values through the PUT untouched.
109
+ description: entry.kind === 'manual' ? entry.description : wl.description,
110
+ activity: entry.kind === 'manual' ? entry.activity : wl.activity,
111
+ ...(remoteChanged(own, wl, entry.date, entry.kind) ? { conflict: true } : {}),
112
+ });
113
+ continue;
114
+ }
115
+ // Ownerless (never pushed) or orphaned (worklog vanished remotely).
116
+ const group = pending.get(`${entry.date}|${jira.issueId}`) ?? { manuals: [], sessions: [] };
117
+ const item = { entry, issueId: jira.issueId, hadOwnership: !!own };
118
+ (entry.kind === 'manual' ? group.manuals : group.sessions).push(item);
119
+ pending.set(`${entry.date}|${jira.issueId}`, group);
120
+ }
121
+ // ── Pass 2: adoption / creation per (date, issue) ──
122
+ for (const [dateIssueKey, group] of pending) {
123
+ const pool = (byDateIssue.get(dateIssueKey) ?? [])
124
+ .filter(w => !accounted.has(w.tempoWorklogId) && !ownedIds.has(w.tempoWorklogId))
125
+ .sort((a, b) => a.tempoWorklogId - b.tempoWorklogId);
126
+ // Manual entries adopt by exact content — an identical unowned worklog
127
+ // IS this entry (either our lost push or the user logging the same thing
128
+ // directly in Tempo); any assignment among equals is semantically equal.
129
+ for (const { entry, issueId, hadOwnership } of [...group.manuals].sort((a, b) => (a.entry.entryId ?? '').localeCompare(b.entry.entryId ?? ''))) {
130
+ const base = {
131
+ date: entry.date, task: entry.task, targetSeconds: entry.totalSeconds,
132
+ issueId, kind: entry.kind, entryId: entry.entryId,
133
+ description: entry.description, activity: entry.activity,
134
+ };
135
+ const idx = pool.findIndex(w => contentMatches(entry, w));
136
+ if (idx !== -1) {
137
+ const wl = pool.splice(idx, 1)[0];
138
+ accounted.add(wl.tempoWorklogId);
139
+ plan.push({ ...base, action: 'update', existingWorklogId: wl.tempoWorklogId, detail: `Re-adopted worklog #${wl.tempoWorklogId}` });
140
+ }
141
+ else {
142
+ plan.push({ ...base, action: 'create', detail: `New (${formatHours(entry.totalSeconds)})`, ...(hadOwnership ? { conflict: true } : {}) });
143
+ }
144
+ }
145
+ // Session aggregate: adopt the single remaining candidate; a multi-way
146
+ // ambiguity is only accepted when the candidates sum to the desired time
147
+ // (a split done in Tempo) — otherwise refuse to guess.
148
+ for (const { entry, issueId, hadOwnership } of group.sessions) {
149
+ const base = {
150
+ date: entry.date, task: entry.task, targetSeconds: entry.totalSeconds,
151
+ issueId, kind: entry.kind,
152
+ };
153
+ if (pool.length === 1) {
154
+ const wl = pool.pop();
155
+ accounted.add(wl.tempoWorklogId);
156
+ const detail = timeDrifts(wl.timeSpentSeconds, entry.totalSeconds)
157
+ ? `Re-adopted worklog #${wl.tempoWorklogId}: ${formatHours(wl.timeSpentSeconds)} → ${formatHours(entry.totalSeconds)}`
158
+ : `Re-adopted worklog #${wl.tempoWorklogId}`;
159
+ plan.push({ ...base, action: 'update', existingWorklogId: wl.tempoWorklogId, detail, description: wl.description, activity: wl.activity });
160
+ }
161
+ else if (pool.length > 1) {
162
+ const sum = pool.reduce((s, w) => s + w.timeSpentSeconds, 0);
163
+ const ids = pool.map(w => w.tempoWorklogId);
164
+ for (const w of pool)
165
+ accounted.add(w.tempoWorklogId);
166
+ pool.length = 0;
167
+ if (!timeDrifts(sum, entry.totalSeconds)) {
168
+ plan.push({ ...base, action: 'skip', detail: `Exists in Tempo (${ids.length} worklogs, ${formatHours(sum)})`, extraWorklogIds: ids });
169
+ }
170
+ else {
171
+ plan.push({ ...base, action: 'error', detail: `Ambiguous: ${ids.length} unowned worklogs (${formatHours(sum)}) vs local ${formatHours(entry.totalSeconds)} — resolve in Tempo`, extraWorklogIds: ids });
172
+ }
173
+ }
174
+ else {
175
+ plan.push({ ...base, action: 'create', detail: `New (${formatHours(entry.totalSeconds)})`, ...(hadOwnership ? { conflict: true } : {}) });
176
+ }
177
+ }
178
+ }
179
+ // ── Pass 3: delete propagation ──
180
+ // Strays: ownership keys inside the range whose local line vanished (the
181
+ // user deleted a session / re-tasked a day after pushing) while the worklog
182
+ // still lives in Tempo. Local state is the source of truth — delete, unless
183
+ // the day file itself is missing (that is data loss, not intent).
184
+ if (options.from && options.to) {
185
+ for (const [key, own] of Object.entries(pushLog)) {
186
+ const [kDate, kTask, kManual] = key.split('|');
187
+ if (kDate < options.from || kDate > options.to)
188
+ continue;
189
+ const wl = snapById.get(own.tempoWorklogId);
190
+ if (!wl || accounted.has(wl.tempoWorklogId))
191
+ continue;
192
+ accounted.add(wl.tempoWorklogId);
193
+ const base = {
194
+ date: kDate, task: kTask, targetSeconds: wl.timeSpentSeconds,
195
+ issueId: wl.issueId, existingWorklogId: wl.tempoWorklogId,
196
+ kind: (kManual ? 'manual' : 'session'),
197
+ ...(kManual ? { entryId: kManual.slice(2) } : {}),
198
+ };
199
+ if (options.datesWithData?.has(kDate)) {
200
+ plan.push({ ...base, action: 'delete', detail: `Deleted locally → removing from Tempo (${formatHours(wl.timeSpentSeconds)})` });
201
+ }
202
+ else {
203
+ plan.push({ ...base, action: 'skip', detail: `Owned worklog kept — no local data for ${kDate}` });
204
+ }
205
+ }
206
+ }
207
+ // Tombstones: explicit local deletions of pushed manual entries.
208
+ for (const t of tombstones) {
209
+ const wl = snapById.get(t.tempoWorklogId);
210
+ if (!wl || accounted.has(wl.tempoWorklogId))
211
+ continue;
212
+ accounted.add(wl.tempoWorklogId);
213
+ plan.push({
214
+ date: t.date, task: t.task, targetSeconds: wl.timeSpentSeconds,
215
+ issueId: wl.issueId, existingWorklogId: wl.tempoWorklogId,
216
+ kind: 'manual', entryId: t.entryId,
217
+ action: 'delete', detail: `Deleted locally → removing from Tempo (${formatHours(wl.timeSpentSeconds)})`,
218
+ });
219
+ }
220
+ // ── Pass 4: unmatched Tempo worklogs — show, never mutate ──
221
+ for (const wl of tempoWorklogs) {
222
+ if (accounted.has(wl.tempoWorklogId))
223
+ continue;
224
+ let taskKey = `issue:${wl.issueId}`;
225
+ for (const [key, jira] of jiraMap) {
226
+ if (jira.issueId === wl.issueId) {
227
+ taskKey = key;
228
+ break;
229
+ }
230
+ }
231
+ plan.push({
232
+ date: wl.startDate, task: taskKey, targetSeconds: wl.timeSpentSeconds,
233
+ action: 'skip', detail: `Tempo only (${formatHours(wl.timeSpentSeconds)})`,
234
+ existingWorklogId: wl.tempoWorklogId, kind: 'session',
235
+ });
236
+ }
237
+ plan.sort((a, b) => a.date.localeCompare(b.date) || a.task.localeCompare(b.task));
238
+ return plan;
239
+ }
240
+ /**
241
+ * Offline drift check for one day — the status side of the same diff. Works
242
+ * without Jira (ownership keys carry date|task), against a month snapshot.
243
+ * Returns human-readable drift lines; empty array = verified parity.
244
+ */
245
+ export function computeDayDrift(date, entries, pushLog, snapById, tombstones = []) {
246
+ const drift = [];
247
+ const claimed = new Set();
248
+ for (const entry of entries) {
249
+ const key = pushLogKey(date, entry.task, entry.kind === 'manual' ? entry.entryId : undefined);
250
+ const label = entry.kind === 'manual' ? `${entry.task} (manual)` : entry.task;
251
+ const own = pushLog[key];
252
+ const wl = own ? snapById.get(own.tempoWorklogId) : undefined;
253
+ if (!own || !wl) {
254
+ drift.push(`${label}: not pushed (${formatHours(entry.totalSeconds)})`);
255
+ continue;
256
+ }
257
+ claimed.add(wl.tempoWorklogId);
258
+ // Field-drift lines only — an entry whose remote copy matches the local
259
+ // desired state stays parity even when the push-log baseline is stale.
260
+ const suffix = remoteChanged(own, wl, date, entry.kind) ? ' — edited in Tempo' : '';
261
+ if (wl.startDate !== date) {
262
+ drift.push(`${label}: moved to ${wl.startDate} in Tempo`);
263
+ }
264
+ if (timeDrifts(wl.timeSpentSeconds, entry.totalSeconds)) {
265
+ drift.push(`${label}: ${formatHours(wl.timeSpentSeconds)} in Tempo vs ${formatHours(entry.totalSeconds)} local${suffix}`);
266
+ }
267
+ if (entry.kind === 'manual' && manualTextDrifts(entry, wl)) {
268
+ drift.push(`${label}: description/activity differ${suffix}`);
269
+ }
270
+ }
271
+ // Strays: ownership keys of this date whose local line is gone while the
272
+ // worklog still lives in Tempo — the next push deletes it.
273
+ for (const [key, own] of Object.entries(pushLog)) {
274
+ if (!key.startsWith(`${date}|`))
275
+ continue;
276
+ const wl = snapById.get(own.tempoWorklogId);
277
+ if (!wl || claimed.has(own.tempoWorklogId))
278
+ continue;
279
+ drift.push(`${key.split('|')[1]}: deleted locally, still in Tempo (${formatHours(wl.timeSpentSeconds)})`);
280
+ }
281
+ // Tombstoned manual entries awaiting the delete pass.
282
+ for (const t of tombstones) {
283
+ if (t.date !== date)
284
+ continue;
285
+ const wl = snapById.get(t.tempoWorklogId);
286
+ if (!wl || claimed.has(t.tempoWorklogId))
287
+ continue;
288
+ drift.push(`${t.task}: pending delete in Tempo (${formatHours(wl.timeSpentSeconds)})`);
289
+ }
290
+ return drift;
291
+ }
292
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../src/push/reconcile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Secrets, TempoApprovalResponse, TempoMetaUnavailableReason } from '../core/types.js';
2
+ /** Drop the whole cache — Tempo-side timeSpentSeconds just changed. */
3
+ export declare function invalidateApprovalCache(): void;
4
+ /** Empty degraded response — also used by callers with no secrets at all. */
5
+ export declare function approvalUnavailable(reason: TempoMetaUnavailableReason): TempoApprovalResponse;
6
+ /**
7
+ * Timesheet approval for the period containing the given month (period =
8
+ * calendar month on approvalPeriod=MONTH instances). Needs both a Jira
9
+ * account (accountId lookup) and a Tempo token with approvals:view. Cached
10
+ * briefly; the cache is dropped after every successful push.
11
+ */
12
+ export declare function resolveMonthApproval(year: number, month: number, secrets: Secrets, forceRefresh?: boolean): Promise<TempoApprovalResponse>;