kankaku-pi 1.0.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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1438 -0
  3. package/dist/adapters/cached-catalog.d.ts +42 -0
  4. package/dist/adapters/cached-catalog.js +121 -0
  5. package/dist/adapters/export-writer.d.ts +13 -0
  6. package/dist/adapters/export-writer.js +28 -0
  7. package/dist/adapters/file-modes.d.ts +20 -0
  8. package/dist/adapters/file-modes.js +34 -0
  9. package/dist/adapters/hub-actions.d.ts +35 -0
  10. package/dist/adapters/hub-actions.js +70 -0
  11. package/dist/adapters/hub-credentials.d.ts +35 -0
  12. package/dist/adapters/hub-credentials.js +58 -0
  13. package/dist/adapters/jsonl-work-log.d.ts +20 -0
  14. package/dist/adapters/jsonl-work-log.js +62 -0
  15. package/dist/adapters/kankaku-dir.d.ts +38 -0
  16. package/dist/adapters/kankaku-dir.js +85 -0
  17. package/dist/adapters/lazy-jsonl-work-log.d.ts +17 -0
  18. package/dist/adapters/lazy-jsonl-work-log.js +31 -0
  19. package/dist/adapters/pocketbase-catalog.d.ts +16 -0
  20. package/dist/adapters/pocketbase-catalog.js +56 -0
  21. package/dist/adapters/pocketbase-client.d.ts +81 -0
  22. package/dist/adapters/pocketbase-client.js +148 -0
  23. package/dist/adapters/pocketbase-sink.d.ts +53 -0
  24. package/dist/adapters/pocketbase-sink.js +181 -0
  25. package/dist/adapters/project-config.d.ts +42 -0
  26. package/dist/adapters/project-config.js +108 -0
  27. package/dist/adapters/report-data.d.ts +12 -0
  28. package/dist/adapters/report-data.js +8 -0
  29. package/dist/adapters/report-views.d.ts +45 -0
  30. package/dist/adapters/report-views.js +73 -0
  31. package/dist/adapters/report.d.ts +112 -0
  32. package/dist/adapters/report.js +236 -0
  33. package/dist/adapters/sync-runner.d.ts +114 -0
  34. package/dist/adapters/sync-runner.js +273 -0
  35. package/dist/adapters/sync-state-store.d.ts +62 -0
  36. package/dist/adapters/sync-state-store.js +188 -0
  37. package/dist/config.d.ts +168 -0
  38. package/dist/config.js +392 -0
  39. package/dist/domain/ancestry-match.d.ts +49 -0
  40. package/dist/domain/ancestry-match.js +82 -0
  41. package/dist/domain/client-label.d.ts +28 -0
  42. package/dist/domain/client-label.js +44 -0
  43. package/dist/domain/day.d.ts +2 -0
  44. package/dist/domain/day.js +8 -0
  45. package/dist/domain/export.d.ts +38 -0
  46. package/dist/domain/export.js +68 -0
  47. package/dist/domain/hub-entry.d.ts +234 -0
  48. package/dist/domain/hub-entry.js +265 -0
  49. package/dist/domain/index.d.ts +19 -0
  50. package/dist/domain/index.js +19 -0
  51. package/dist/domain/intervals.d.ts +17 -0
  52. package/dist/domain/intervals.js +43 -0
  53. package/dist/domain/registry-health.d.ts +49 -0
  54. package/dist/domain/registry-health.js +58 -0
  55. package/dist/domain/segment-rule.d.ts +10 -0
  56. package/dist/domain/segment-rule.js +1 -0
  57. package/dist/domain/subagent-profile.d.ts +278 -0
  58. package/dist/domain/subagent-profile.js +418 -0
  59. package/dist/domain/sync-plan.d.ts +151 -0
  60. package/dist/domain/sync-plan.js +196 -0
  61. package/dist/domain/task-view.d.ts +117 -0
  62. package/dist/domain/task-view.js +428 -0
  63. package/dist/domain/work-record.d.ts +236 -0
  64. package/dist/domain/work-record.js +91 -0
  65. package/dist/domain/work-target.d.ts +101 -0
  66. package/dist/domain/work-target.js +149 -0
  67. package/dist/domain/work-tracker.d.ts +90 -0
  68. package/dist/domain/work-tracker.js +405 -0
  69. package/dist/hub/index.d.ts +25 -0
  70. package/dist/hub/index.js +25 -0
  71. package/dist/ports/catalog.d.ts +31 -0
  72. package/dist/ports/catalog.js +1 -0
  73. package/dist/ports/clock.d.ts +3 -0
  74. package/dist/ports/clock.js +1 -0
  75. package/dist/ports/index.d.ts +11 -0
  76. package/dist/ports/index.js +1 -0
  77. package/dist/ports/inflight-store.d.ts +15 -0
  78. package/dist/ports/inflight-store.js +1 -0
  79. package/dist/ports/process-registry.d.ts +72 -0
  80. package/dist/ports/process-registry.js +1 -0
  81. package/dist/ports/work-log.d.ts +14 -0
  82. package/dist/ports/work-log.js +1 -0
  83. package/dist/ports/work-sink.d.ts +39 -0
  84. package/dist/ports/work-sink.js +1 -0
  85. package/package.json +66 -0
  86. package/src/adapters/agent-info.ts +86 -0
  87. package/src/adapters/ancestry.ts +260 -0
  88. package/src/adapters/cached-catalog.ts +147 -0
  89. package/src/adapters/export-writer.ts +33 -0
  90. package/src/adapters/file-inflight-store.ts +115 -0
  91. package/src/adapters/file-modes.ts +35 -0
  92. package/src/adapters/hub-actions.ts +82 -0
  93. package/src/adapters/hub-credentials.ts +95 -0
  94. package/src/adapters/jsonl-work-log.ts +67 -0
  95. package/src/adapters/kankaku-command.ts +717 -0
  96. package/src/adapters/kankaku-dir.ts +102 -0
  97. package/src/adapters/lazy-file-inflight-store.ts +43 -0
  98. package/src/adapters/lazy-jsonl-work-log.ts +39 -0
  99. package/src/adapters/machine-process-registry.ts +256 -0
  100. package/src/adapters/panel/kankaku-panel.ts +419 -0
  101. package/src/adapters/panel/panel-items.ts +87 -0
  102. package/src/adapters/panel/panel-lines.ts +13 -0
  103. package/src/adapters/panel/panel-theme.ts +32 -0
  104. package/src/adapters/panel/screens/about.ts +69 -0
  105. package/src/adapters/panel/screens/doctor.ts +89 -0
  106. package/src/adapters/panel/screens/export.ts +123 -0
  107. package/src/adapters/panel/screens/report.ts +143 -0
  108. package/src/adapters/panel/screens/sync.ts +136 -0
  109. package/src/adapters/panel/screens/target.ts +384 -0
  110. package/src/adapters/pi-tracker.ts +753 -0
  111. package/src/adapters/pocketbase-catalog.ts +89 -0
  112. package/src/adapters/pocketbase-client.ts +197 -0
  113. package/src/adapters/pocketbase-sink.ts +236 -0
  114. package/src/adapters/process-identity-memo.ts +102 -0
  115. package/src/adapters/process-identity.ts +162 -0
  116. package/src/adapters/project-config.ts +116 -0
  117. package/src/adapters/report-data.ts +13 -0
  118. package/src/adapters/report-views.ts +98 -0
  119. package/src/adapters/report.ts +335 -0
  120. package/src/adapters/session-client.ts +116 -0
  121. package/src/adapters/session-dir.ts +28 -0
  122. package/src/adapters/session-target.ts +431 -0
  123. package/src/adapters/status-bar.ts +86 -0
  124. package/src/adapters/subagent-startup.ts +66 -0
  125. package/src/adapters/sync-runner.ts +340 -0
  126. package/src/adapters/sync-state-store.ts +227 -0
  127. package/src/adapters/target-picker.ts +127 -0
  128. package/src/config.ts +536 -0
  129. package/src/domain/ancestry-match.ts +84 -0
  130. package/src/domain/client-label.ts +56 -0
  131. package/src/domain/day.ts +8 -0
  132. package/src/domain/export.ts +107 -0
  133. package/src/domain/hub-entry.ts +433 -0
  134. package/src/domain/index.ts +19 -0
  135. package/src/domain/intervals.ts +53 -0
  136. package/src/domain/panel-model.ts +270 -0
  137. package/src/domain/registry-health.ts +87 -0
  138. package/src/domain/segment-rule.ts +10 -0
  139. package/src/domain/subagent-profile.ts +495 -0
  140. package/src/domain/sync-plan.ts +266 -0
  141. package/src/domain/task-view.ts +526 -0
  142. package/src/domain/work-record.ts +320 -0
  143. package/src/domain/work-target.ts +234 -0
  144. package/src/domain/work-tracker.ts +485 -0
  145. package/src/extension.ts +346 -0
  146. package/src/hub/index.ts +25 -0
  147. package/src/ports/catalog.ts +33 -0
  148. package/src/ports/clock.ts +3 -0
  149. package/src/ports/index.ts +11 -0
  150. package/src/ports/inflight-store.ts +16 -0
  151. package/src/ports/process-registry.ts +75 -0
  152. package/src/ports/work-log.ts +15 -0
  153. package/src/ports/work-sink.ts +35 -0
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Decide which tasks a sync run should push, without touching the network
3
+ * or the filesystem. Pure, no I/O — see `sync-runner.ts` for the adapter
4
+ * that drives this with a real clock, `WorkLog` and `WorkSink`.
5
+ *
6
+ * A task is not final the moment it is first written: a background
7
+ * subagent can settle after its orchestrator and extend the task's union
8
+ * (see `task-view.ts`), which is exactly why every sync also revisits a
9
+ * trailing window behind the watermark instead of only pushing brand-new
10
+ * tasks (proposal §6.0).
11
+ */
12
+ import { computeCostQuality, computeSubagentLinkage } from "./hub-entry.js";
13
+ const DEFAULT_WINDOW_HOURS = 24;
14
+ /** How many already-synced, out-of-window rows one incremental run corrects at most; the rest wait for the next run (or `sync all`). */
15
+ export const MAX_CORRECTIONS_PER_RUN = 50;
16
+ function windowMs(hours) {
17
+ return hours * 60 * 60 * 1000;
18
+ }
19
+ /** Stable (key-sorted) JSON serialization so field order never changes a hash. */
20
+ function stableStringify(value) {
21
+ if (value === null || typeof value !== "object")
22
+ return JSON.stringify(value);
23
+ if (Array.isArray(value))
24
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
25
+ const record = value;
26
+ const keys = Object.keys(record).sort();
27
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
28
+ }
29
+ /** FNV-1a 32-bit over a UTF-16 code-unit stream — not a security hash, just a cheap deterministic change fingerprint. */
30
+ function fingerprint(input) {
31
+ let hash = 0x811c9dc5;
32
+ for (let i = 0; i < input.length; i++) {
33
+ hash ^= input.charCodeAt(i);
34
+ hash = Math.imul(hash, 0x01000193);
35
+ }
36
+ return (hash >>> 0).toString(16).padStart(8, "0");
37
+ }
38
+ /**
39
+ * Content hash of everything a re-sync could change: the measurement
40
+ * fields (never the assignment — a reassignment made in the web is never
41
+ * visible locally, and must never trigger a resync on its own). A task
42
+ * whose hash matches the last stored one is unchanged and can be skipped
43
+ * without a request. Includes the derived measurement-quality fields
44
+ * (`domain/hub-entry.ts`) too, not just the raw numbers they are computed
45
+ * from: a background subagent that joins *after* this task was first
46
+ * synced can turn `cost_quality`/`subagent_linkage` from `"unknown"`/
47
+ * `"unlinked"` into a better answer without any of the other numeric
48
+ * fields necessarily changing (e.g. a joined child with no cost of its own
49
+ * still flips `subagent_linkage`) — that must still trigger a resync.
50
+ * `waiting_quality` is a true constant (`domain/hub-entry.ts`'s
51
+ * `computeWaitingQuality`) and is deliberately left out: it can never
52
+ * change between two evaluations of the same task. `sessionDir` is also a
53
+ * measurement-style field (`domain/hub-entry.ts`'s `session_dir`, sent on
54
+ * both create and update) — its own change, e.g. a resume that switches to
55
+ * a non-default session directory, must trigger a resync on its own even
56
+ * when nothing else changed.
57
+ */
58
+ export function computeTaskContentHash(task) {
59
+ return fingerprint(stableStringify({
60
+ endedAt: task.endedAt,
61
+ status: task.status,
62
+ wallMs: task.wallMs,
63
+ waitingMs: task.waitingMs,
64
+ workMs: task.workMs,
65
+ subagentCount: task.subagents.length,
66
+ usage: task.usage,
67
+ segments: task.segments,
68
+ costQuality: computeCostQuality(task),
69
+ subagentLinkage: computeSubagentLinkage(task),
70
+ sessionDir: task.sessionDir,
71
+ // `undefined` is dropped by the serialiser, so a task that never knew
72
+ // its reasoning effort keeps the hash it had before this field existed.
73
+ thinkingLevel: task.orchestrator.thinkingLevel,
74
+ // A task gaining a who-measured identity
75
+ // (domain/hub-entry.ts#resolveTaskIdentity) must resync, since it
76
+ // changes which agent/plugin the row is attributed to; the version
77
+ // fields are left out on purpose so a version-only bump alone does not
78
+ // force a resync. The keys are added CONDITIONALLY: `stableStringify`
79
+ // renders an `undefined` value as `"agent":undefined`, so an
80
+ // unconditional key would change every legacy task's hash and force a
81
+ // full resync right after upgrading (see the golden-hash test).
82
+ ...(task.orchestrator.agent !== undefined ? { agent: task.orchestrator.agent } : {}),
83
+ ...(task.orchestrator.plugin !== undefined ? { plugin: task.orchestrator.plugin } : {}),
84
+ }));
85
+ }
86
+ /**
87
+ * Decide which tasks need a request this run.
88
+ *
89
+ * Eligibility: every task, when there is no state yet, the state's
90
+ * `target` differs from the configured hub URL, or `options.full` is set
91
+ * (a full sync); otherwise only tasks with `endedAt` after
92
+ * `syncedThrough - window`. Within the eligible set, a task is only
93
+ * included in `toSync` when its current content hash differs from the one
94
+ * stored in `state.hashes` (absent, i.e. never synced, always counts as
95
+ * different).
96
+ */
97
+ export function planSync(tasks, state, options) {
98
+ const windowHours = options.windowHours ?? DEFAULT_WINDOW_HOURS;
99
+ const isFullSync = options.full === true || state === undefined || state.target !== options.target;
100
+ const sorted = [...tasks].sort((a, b) => Date.parse(a.endedAt) - Date.parse(b.endedAt));
101
+ let eligible;
102
+ let outsideWindow;
103
+ if (isFullSync) {
104
+ eligible = sorted;
105
+ outsideWindow = [];
106
+ }
107
+ else {
108
+ const syncedThroughMs = state.syncedThrough ? Date.parse(state.syncedThrough) : Number.NEGATIVE_INFINITY;
109
+ const cutoff = syncedThroughMs - windowMs(windowHours);
110
+ eligible = sorted.filter((task) => Date.parse(task.endedAt) > cutoff);
111
+ outsideWindow = sorted.filter((task) => Date.parse(task.endedAt) <= cutoff);
112
+ }
113
+ // A state written for another hub contributes nothing, not even its
114
+ // hashes: the new hub has none of these rows, so a task that matched
115
+ // the OLD hub's hash must still be pushed. `isFullSync` alone only
116
+ // widened the window; without this gate every task looked "unchanged".
117
+ const hashes = state !== undefined && state.target === options.target ? state.hashes : {};
118
+ const changed = (task) => hashes[task.id] !== computeTaskContentHash(task);
119
+ // A row the hub ALREADY holds is corrected wherever it sits: the window
120
+ // bounds how far back NEW work is looked for, never whether a known row
121
+ // may go stale. A child can move between tasks (task-view.ts's rescue
122
+ // join, or a crash-recovered parent appearing later), so a task can
123
+ // SHRINK — skipping it would leave its old, larger cost on the hub next
124
+ // to the row the money moved to.
125
+ // Newest first and capped: anything that invalidates many stored hashes at
126
+ // once (a new field in the content hash, a damaged state file) must drain
127
+ // over several runs, never as one burst from an automatic sync. New work
128
+ // goes FIRST and corrections last, so a correction the hub keeps failing
129
+ // (the runner stops at the first transport error) can never hold newer
130
+ // rows back; the runner's watermark is a max, so the order is safe for it.
131
+ const allCorrections = outsideWindow.filter((task) => hashes[task.id] !== undefined && changed(task)).reverse();
132
+ const knownAndChanged = isFullSync ? allCorrections : allCorrections.slice(0, MAX_CORRECTIONS_PER_RUN);
133
+ const correctionsDeferred = allCorrections.length - knownAndChanged.length;
134
+ const toSync = [...eligible.filter(changed), ...knownAndChanged];
135
+ // R3: cheap, pure visibility into a task this incremental run's window
136
+ // will not look at and that this hub has NEVER received (no stored hash)
137
+ // — see SyncPlan's doc comment. A known row that changed is a correction
138
+ // and is handled above, not reported here.
139
+ const staleOutsideWindow = outsideWindow.filter((task) => hashes[task.id] === undefined);
140
+ return { toSync, unchangedCount: eligible.length - eligible.filter(changed).length, isFullSync, staleOutsideWindow, correctionsDeferred };
141
+ }
142
+ /**
143
+ * Drop hash entries for task ids that no longer exist in `tasks` (which
144
+ * should not normally happen since `worklog.jsonl` is append-only — this
145
+ * is a defensive backstop, not the normal path). `sync-state.json`'s
146
+ * `hashes` are otherwise kept **forever** for every task this process
147
+ * still knows about, regardless of how far outside the revisit window its
148
+ * `endedAt` has fallen (G2 fix).
149
+ *
150
+ * This used to also drop a hash once its task's `endedAt` fell behind
151
+ * `syncedThrough - window` — but `planSync`'s `staleOutsideWindow` treats a
152
+ * *missing* hash exactly like "content changed" (there is no third state
153
+ * for "unchanged but I forgot"), so that window-based pruning made every
154
+ * task older than the window look permanently changed, forever, the moment
155
+ * its hash was first pruned: `/kankaku sync status` would report a
156
+ * never-shrinking "changed outside the window" count that trained users
157
+ * to ignore it (the bug this rewrite fixes).
158
+ *
159
+ * Never pruning by window instead means `hashes` grows with the total
160
+ * number of distinct tasks a directory has ever synced, not with time — an
161
+ * FNV-1a hash is 8 hex chars and a task id (a `crypto.randomUUID()`) is 36,
162
+ * so each retained entry costs roughly 50 bytes of JSON. Measured: 10,000
163
+ * entries serialize to well under 1MB (see `tests/sync-plan.test.ts`'s
164
+ * state-size-bound test) — even a directory with a decade of daily,
165
+ * multi-task-per-day history stays a small, instantly-parseable file. A
166
+ * coarser design (e.g. one rolling digest per closed day) would bound the
167
+ * file even tighter, but cannot answer "which specific task changed" —
168
+ * `staleOutsideWindow` needs exactly that, per-task precision, to stay
169
+ * useful — so it was rejected in favour of this simpler, still-cheap
170
+ * per-task scheme.
171
+ *
172
+ * No migration is needed for an existing `sync-state.json`: it already has
173
+ * exactly this shape (`Record<taskId, hash>`), just with some outside-
174
+ * window entries already missing from a build that pruned them. The first
175
+ * sync after upgrading treats each of those exactly like "never synced" —
176
+ * a real fact this process cannot know is false, since the old hash is
177
+ * genuinely gone — and reports it once via `staleOutsideWindow`; once that
178
+ * task's hash is recorded again (an ordinary `sync all`, or simply being
179
+ * observed unchanged), this function never drops it again. See
180
+ * `tests/sync-plan.test.ts`'s "first sync after upgrading" test.
181
+ *
182
+ * Accumulated in a `Map` and emitted via `Object.fromEntries` (never
183
+ * `pruned[id] = ...` on a plain object), since a task id ultimately traces
184
+ * back to free-text worklog content: a value like `__proto__` written to a
185
+ * plain object would silently no-op (the inherited accessor ignores a
186
+ * non-object assignment) instead of being kept as an own property.
187
+ */
188
+ export function pruneHashes(hashes, tasks) {
189
+ const knownIds = new Set(tasks.map((task) => task.id));
190
+ const pruned = new Map();
191
+ for (const [id, hash] of Object.entries(hashes)) {
192
+ if (knownIds.has(id))
193
+ pruned.set(id, hash);
194
+ }
195
+ return Object.fromEntries(pruned);
196
+ }
@@ -0,0 +1,117 @@
1
+ import type { UsageTotals, WorkRecord, WorkStatus } from "./work-record.ts";
2
+ /**
3
+ * One orchestrator run plus every subagent it spawned, with `wallMs`
4
+ * recomputed as the union of the orchestrator's span and each child's span
5
+ * — never their sum — because background children keep running in parallel
6
+ * after the orchestrator settles.
7
+ */
8
+ export interface TaskView {
9
+ id: string;
10
+ sessionId?: string;
11
+ project: string;
12
+ prompt: string;
13
+ startedAt: string;
14
+ endedAt: string;
15
+ wallMs: number;
16
+ waitingMs: number;
17
+ workMs: number;
18
+ status: WorkStatus;
19
+ orchestrator: WorkRecord;
20
+ subagents: WorkRecord[];
21
+ usage: UsageTotals;
22
+ /** Who this task is billed to, from the orchestrator record only — subagent children do not carry their own. */
23
+ client?: string;
24
+ /** pi's session display name, from the orchestrator record. */
25
+ sessionName?: string;
26
+ /** Non-default session directory, from the orchestrator record. See `WorkRecordMetadata.sessionDir`. Local-only today — no hub field yet. */
27
+ sessionDir?: string;
28
+ /** Hub client record id, from the orchestrator record only. See `domain/work-target.ts`. */
29
+ clientId?: string;
30
+ /** Hub client display name, from the orchestrator record only. */
31
+ clientName?: string;
32
+ /** Hub project record id, from the orchestrator record only. */
33
+ projectId?: string;
34
+ /** Hub project display name, from the orchestrator record only. */
35
+ projectName?: string;
36
+ /** Hub task record id, from the orchestrator record only. See `domain/work-target.ts#HubTask`. */
37
+ hubTaskId?: string;
38
+ /** Hub task title, from the orchestrator record only. */
39
+ hubTaskTitle?: string;
40
+ /**
41
+ * Per-tag total milliseconds across the orchestrator and every subagent,
42
+ * summed rather than unioned: unlike `wallMs`, segment intervals are not
43
+ * persisted on disk, so once a record settles its per-tag total is all
44
+ * that remains, and there is nothing left to union across records.
45
+ */
46
+ segments: Record<string, number>;
47
+ }
48
+ /** One or more tasks grouped by their pi session, with the same union rule. */
49
+ export interface SessionView {
50
+ sessionId: string;
51
+ project: string;
52
+ startedAt: string;
53
+ endedAt: string;
54
+ wallMs: number;
55
+ waitingMs: number;
56
+ workMs: number;
57
+ tasks: TaskView[];
58
+ usage: UsageTotals;
59
+ /** Per-tag total milliseconds summed across the session's tasks. See {@link TaskView.segments}. */
60
+ segments: Record<string, number>;
61
+ }
62
+ /**
63
+ * Sum several {@link UsageTotals}, tolerating a missing entry (a record
64
+ * without a `usage` field) and missing or non-finite numeric fields on an
65
+ * entry — both treated as zero rather than corrupting the sum with
66
+ * `undefined`/`NaN`.
67
+ */
68
+ export declare function sumUsage(totals: Array<Partial<UsageTotals> | undefined>): UsageTotals;
69
+ export declare function dedupeById(records: WorkRecord[]): WorkRecord[];
70
+ /**
71
+ * Build one {@link TaskView} per *confirmed* orchestrator record, sorted by
72
+ * `startedAt`. An orchestrator-role record flagged `roleConfidence:
73
+ * "uncertain"` (ADR 0022) never anchors a task here — see
74
+ * {@link isConfirmedOrchestrator} and {@link uncertainRecords}.
75
+ */
76
+ export declare function buildTasks(allRecords: WorkRecord[]): TaskView[];
77
+ /** Subagent records that could not be matched to any orchestrator record. */
78
+ export declare function orphanSubagents(records: WorkRecord[]): WorkRecord[];
79
+ /**
80
+ * Orchestrator-role records that could not be positively proven top-level
81
+ * (ADR 0022's "uncertain" state): no recognised child-env-marker matched,
82
+ * but a live tracked ancestor process was found. Never counted as a new
83
+ * task ({@link buildTasks} excludes them) and never synced as one, but
84
+ * never dropped either — surfaced here so `/kankaku doctor` and the report
85
+ * hint (SUBAGENT-REQ-017) can make the gap visible instead of silent.
86
+ */
87
+ export declare function uncertainRecords(records: WorkRecord[]): WorkRecord[];
88
+ /** One cluster of confirmed-orchestrator records sharing a pid with overlapping `[startedAt, settledAt]` windows — see {@link detectSameProcessOverlaps}. */
89
+ export interface SameProcessOverlap {
90
+ pid: number;
91
+ recordIds: string[];
92
+ /** The union (never the sum) of every clustered record's own wall-time window, via `unionMs`. */
93
+ unionedWallMs: number;
94
+ }
95
+ /**
96
+ * SUBAGENT-REQ-015: flag confirmed-orchestrator records that share an OS
97
+ * pid AND overlap in time — a pattern that should never occur if pi only
98
+ * ever runs one session per process at a time, but is the observable
99
+ * signature an in-process nested session mechanism (if one existed) would
100
+ * leave behind: two independent `WorkTracker` records, same pid, running
101
+ * concurrently. Purely informational (`/kankaku doctor` reads this, see
102
+ * `adapters/kankaku-command.ts`) — it never changes {@link buildTasks}'
103
+ * own per-task `wallMs`, so a plain pi run or today's gentle-pi setup is
104
+ * completely unaffected; each flagged record still anchors its own
105
+ * `TaskView` exactly as before. `unionedWallMs` is provided (via the
106
+ * existing `unionMs` primitive, ADR 0006 — the interval-union rule stays
107
+ * in exactly this one place) so a human reading the doctor report can see
108
+ * what the corrected total would be, without kankaku silently changing any
109
+ * number on its own.
110
+ */
111
+ export declare function detectSameProcessOverlaps(records: WorkRecord[]): SameProcessOverlap[];
112
+ /**
113
+ * Group tasks by `sessionId` (tasks without one fall under `"unknown"`).
114
+ * `wallMs` is the union of every interval — orchestrator and subagent alike
115
+ * — across all of the session's tasks, not a sum of per-task `wallMs`.
116
+ */
117
+ export declare function buildSessions(tasks: TaskView[]): SessionView[];