@proletariat/cli 0.3.111 → 0.3.112

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 (52) hide show
  1. package/dist/commands/gateway/connect.d.ts +33 -0
  2. package/dist/commands/gateway/connect.js +130 -0
  3. package/dist/commands/gateway/connect.js.map +1 -0
  4. package/dist/commands/gateway/disconnect.d.ts +21 -0
  5. package/dist/commands/gateway/disconnect.js +69 -0
  6. package/dist/commands/gateway/disconnect.js.map +1 -0
  7. package/dist/commands/gateway/start.d.ts +23 -0
  8. package/dist/commands/gateway/start.js +133 -0
  9. package/dist/commands/gateway/start.js.map +1 -0
  10. package/dist/commands/gateway/status.d.ts +16 -0
  11. package/dist/commands/gateway/status.js +76 -0
  12. package/dist/commands/gateway/status.js.map +1 -0
  13. package/dist/commands/gateway/test.d.ts +22 -0
  14. package/dist/commands/gateway/test.js +83 -0
  15. package/dist/commands/gateway/test.js.map +1 -0
  16. package/dist/commands/reconcile.d.ts +29 -0
  17. package/dist/commands/reconcile.js +140 -0
  18. package/dist/commands/reconcile.js.map +1 -0
  19. package/dist/commands/work/ship.js +131 -61
  20. package/dist/commands/work/ship.js.map +1 -1
  21. package/dist/lib/gateway/channel-factory.d.ts +13 -0
  22. package/dist/lib/gateway/channel-factory.js +37 -0
  23. package/dist/lib/gateway/channel-factory.js.map +1 -0
  24. package/dist/lib/gateway/channels/telegram.d.ts +115 -0
  25. package/dist/lib/gateway/channels/telegram.js +215 -0
  26. package/dist/lib/gateway/channels/telegram.js.map +1 -0
  27. package/dist/lib/gateway/router.d.ts +84 -0
  28. package/dist/lib/gateway/router.js +140 -0
  29. package/dist/lib/gateway/router.js.map +1 -0
  30. package/dist/lib/gateway/session-poker.d.ts +35 -0
  31. package/dist/lib/gateway/session-poker.js +85 -0
  32. package/dist/lib/gateway/session-poker.js.map +1 -0
  33. package/dist/lib/gateway/types.d.ts +124 -0
  34. package/dist/lib/gateway/types.js +17 -0
  35. package/dist/lib/gateway/types.js.map +1 -0
  36. package/dist/lib/machine-db.d.ts +87 -0
  37. package/dist/lib/machine-db.js +135 -0
  38. package/dist/lib/machine-db.js.map +1 -1
  39. package/dist/lib/pr/index.d.ts +34 -2
  40. package/dist/lib/pr/index.js +95 -4
  41. package/dist/lib/pr/index.js.map +1 -1
  42. package/dist/lib/reconcile/core.d.ts +62 -0
  43. package/dist/lib/reconcile/core.js +137 -0
  44. package/dist/lib/reconcile/core.js.map +1 -0
  45. package/dist/lib/reconcile/index.d.ts +54 -0
  46. package/dist/lib/reconcile/index.js +377 -0
  47. package/dist/lib/reconcile/index.js.map +1 -0
  48. package/dist/lib/reconcile/types.d.ts +133 -0
  49. package/dist/lib/reconcile/types.js +16 -0
  50. package/dist/lib/reconcile/types.js.map +1 -0
  51. package/oclif.manifest.json +2534 -2192
  52. package/package.json +1 -1
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Tier 2 State Reconciler — Runner (PRLT-1280)
3
+ *
4
+ * This module owns the IO: it talks to the database, the GitHub CLI, and
5
+ * the provider adapter layer. The pure rule logic lives in ./core.ts so
6
+ * it can be unit-tested without these dependencies.
7
+ *
8
+ * Scope (narrow — the ticket is very explicit about what this is NOT):
9
+ * - Single idempotent reconciliation function on a schedule
10
+ * - Provider-agnostic — the reconciler does not branch on provider type
11
+ * - Fires normal state-transition code path so hooks & cleanup fire too
12
+ *
13
+ * Everything outside that — webhook listeners, LLM escalation, human
14
+ * inbox, supervision tree — is explicitly out of scope per PRLT-1280.
15
+ */
16
+ import { PMO_TABLES } from '../pmo/schema.js';
17
+ import { resolveTicketProvider } from '../providers/resolver.js';
18
+ import { getPRForBranch, getPRByNumber } from '../pr/index.js';
19
+ import { getWorkflowConfig } from '../work-lifecycle/settings.js';
20
+ import { buildTransition, deriveExpectedState, formatTransitionLogLine } from './core.js';
21
+ export { deriveExpectedState, buildTransition, formatTransitionLogLine } from './core.js';
22
+ /**
23
+ * Non-terminal state categories that the reconciler scans.
24
+ *
25
+ * The ticket spec says:
26
+ * "Query all tickets in non-terminal states across all connected
27
+ * providers (Linear, PMO, Notion, Asana, Jira, Shortcut)"
28
+ *
29
+ * Terminal states (`completed`, `canceled`) are deliberately excluded —
30
+ * the reconciler never resurrects a closed ticket.
31
+ */
32
+ const NON_TERMINAL_CATEGORIES = [
33
+ 'triage',
34
+ 'backlog',
35
+ 'unstarted',
36
+ 'started',
37
+ ];
38
+ /**
39
+ * Default live PR lookup. Always goes to the remote — the Tier 2
40
+ * reconciler must not depend on any local PR cache.
41
+ */
42
+ export const defaultPRLookup = (ref, cwd) => {
43
+ switch (ref.kind) {
44
+ case 'branch':
45
+ return getPRForBranch(ref.branch, cwd);
46
+ case 'number':
47
+ return getPRByNumber(ref.number, cwd);
48
+ case 'url': {
49
+ // `gh pr view <url>` works with either a number or URL; reuse it.
50
+ const num = parsePRNumberFromUrl(ref.url);
51
+ if (num !== null)
52
+ return getPRByNumber(num, cwd);
53
+ return null;
54
+ }
55
+ }
56
+ };
57
+ /**
58
+ * Run one reconciliation cycle.
59
+ *
60
+ * Strictly idempotent: if a ticket is already in its expected state the
61
+ * runner computes no transition and touches nothing. Running twice
62
+ * back-to-back produces no redundant side effects.
63
+ */
64
+ export async function runReconcile(db, storage, options = {}) {
65
+ const startedAt = new Date();
66
+ const { dryRun = false, cwd, log, projectId } = options;
67
+ const prLookup = options.prLookup ?? defaultPRLookup;
68
+ // Workflow config is used to resolve "Done" / "In Progress" / "Review"
69
+ // column names for the ticket's board. Fall back gracefully if the
70
+ // settings table is missing (older databases, unit tests).
71
+ let workflowConfig;
72
+ try {
73
+ workflowConfig = getWorkflowConfig(db);
74
+ }
75
+ catch {
76
+ workflowConfig = undefined;
77
+ }
78
+ // Gather every non-terminal ticket in scope. Either one project if
79
+ // -P was passed, or every project in the workspace.
80
+ const projectIds = projectId ? [projectId] : await listAllProjectIds(storage);
81
+ const tickets = [];
82
+ for (const pid of projectIds) {
83
+ for (const category of NON_TERMINAL_CATEGORIES) {
84
+ try {
85
+ // eslint-disable-next-line no-await-in-loop -- sequential is fine; small N
86
+ const batch = await storage.listTickets(pid, { statusCategory: category });
87
+ tickets.push(...batch);
88
+ }
89
+ catch {
90
+ // Tolerate transient read errors on a single category; keep going.
91
+ }
92
+ }
93
+ }
94
+ log?.(`Scanning ${tickets.length} non-terminal ticket(s) across ${projectIds.length} project(s)...`);
95
+ const transitions = [];
96
+ const errors = [];
97
+ for (const ticket of tickets) {
98
+ try {
99
+ const linkedPRs = gatherLinkedPRs(db, ticket, prLookup, cwd);
100
+ if (linkedPRs.length === 0) {
101
+ continue;
102
+ }
103
+ // Resolve the provider for this ticket so we can log which backend
104
+ // owns it. The actual move happens through the same resolver later
105
+ // in applyTransition() — this is just for the transition record.
106
+ const providerName = providerNameFor(db, storage, ticket);
107
+ const decision = deriveExpectedState(toReconcileTicket(ticket), linkedPRs, workflowConfig);
108
+ if (!decision)
109
+ continue;
110
+ transitions.push(buildTransition({
111
+ ticket: toReconcileTicket(ticket),
112
+ provider: providerName,
113
+ targetState: decision.targetState,
114
+ reason: decision.reason,
115
+ driver: decision.driver,
116
+ }));
117
+ }
118
+ catch (err) {
119
+ errors.push({ ticketId: ticket.id, error: err instanceof Error ? err.message : String(err) });
120
+ }
121
+ }
122
+ // Apply (or dry-run) every transition. Failures on one ticket do not
123
+ // stop the others — the reconciler is a best-effort safety net.
124
+ const applied = [];
125
+ const skipped = [];
126
+ const failed = [];
127
+ for (const transition of transitions) {
128
+ log?.(formatTransitionLogLine(transition));
129
+ if (dryRun) {
130
+ skipped.push(transition);
131
+ continue;
132
+ }
133
+ try {
134
+ // eslint-disable-next-line no-await-in-loop
135
+ await applyTransition(db, storage, transition);
136
+ applied.push(transition);
137
+ // Dual-identity tickets: if the primary provider is NOT local PMO,
138
+ // the local PMO board may still hold a stale mirror row. Nudge it
139
+ // to the same column so the local kanban view does not drift.
140
+ if (transition.provider !== 'pmo') {
141
+ // eslint-disable-next-line no-await-in-loop
142
+ await reconcilePmoMirror(db, storage, transition, log);
143
+ }
144
+ }
145
+ catch (err) {
146
+ failed.push({
147
+ transition,
148
+ error: err instanceof Error ? err.message : String(err),
149
+ });
150
+ }
151
+ }
152
+ const finishedAt = new Date();
153
+ return {
154
+ checked: tickets.length,
155
+ applied,
156
+ skipped,
157
+ failed,
158
+ errors,
159
+ startedAt: startedAt.toISOString(),
160
+ finishedAt: finishedAt.toISOString(),
161
+ };
162
+ }
163
+ // -----------------------------------------------------------------------------
164
+ // PR lookup
165
+ // -----------------------------------------------------------------------------
166
+ /**
167
+ * Gather every PR linked to a ticket by:
168
+ * 1. the ticket's branch (if set), and
169
+ * 2. PR URLs stored in `pmo_external_execution_prs` under any of the
170
+ * ticket's external-source identities.
171
+ *
172
+ * Every lookup goes through the live PR lookup function — no cache.
173
+ */
174
+ function gatherLinkedPRs(db, ticket, prLookup, cwd) {
175
+ const results = [];
176
+ const seen = new Set(); // de-dupe by PR number
177
+ const push = (pr, source) => {
178
+ if (!pr)
179
+ return;
180
+ if (seen.has(pr.number))
181
+ return;
182
+ seen.add(pr.number);
183
+ results.push({ pr, source });
184
+ };
185
+ // 1. Ticket branch
186
+ if (ticket.branch) {
187
+ push(prLookup({ kind: 'branch', branch: ticket.branch }, cwd), 'ticket_branch');
188
+ }
189
+ // 2. PR URLs stored in the external execution map for this ticket.
190
+ // A ticket can carry multiple external identities (metadata.external_id,
191
+ // external_key) and can have been linked to multiple PRs over time.
192
+ const prUrls = listLinkedPrUrls(db, ticket);
193
+ for (const url of prUrls) {
194
+ push(prLookup({ kind: 'url', url }, cwd), 'external_map');
195
+ }
196
+ return results;
197
+ }
198
+ /**
199
+ * Read every PR URL stored for this ticket in `pmo_external_execution_prs`.
200
+ * Keyed by (provider, external_id) — the reconciler doesn't care which
201
+ * provider it came from, so we union across all of them.
202
+ */
203
+ function listLinkedPrUrls(db, ticket) {
204
+ const urls = new Set();
205
+ const md = ticket.metadata ?? {};
206
+ // Collect every identifier that might key the external mapping table.
207
+ const externalIds = new Set();
208
+ if (md.external_id)
209
+ externalIds.add(md.external_id);
210
+ if (md.external_key)
211
+ externalIds.add(md.external_key);
212
+ // A PMO-native ticket's id can also appear in the map under provider='pmo'.
213
+ externalIds.add(ticket.id);
214
+ if (externalIds.size === 0)
215
+ return [];
216
+ let stmt;
217
+ try {
218
+ stmt = db.prepare(`
219
+ SELECT pr_url FROM ${PMO_TABLES.external_execution_prs}
220
+ WHERE external_id = ?
221
+ `);
222
+ }
223
+ catch {
224
+ // Table may not exist yet (fresh DB or older schema).
225
+ return [];
226
+ }
227
+ for (const extId of externalIds) {
228
+ try {
229
+ const rows = stmt.all(extId);
230
+ for (const row of rows)
231
+ urls.add(row.pr_url);
232
+ }
233
+ catch {
234
+ // Non-fatal: skip this id.
235
+ }
236
+ }
237
+ return [...urls];
238
+ }
239
+ /**
240
+ * Parse a PR number out of a github.com URL.
241
+ * Accepts both `/pull/123` and `/pull/123/...` shapes.
242
+ */
243
+ function parsePRNumberFromUrl(url) {
244
+ const m = url.match(/\/pull\/(\d+)(?:\b|\/)/);
245
+ if (!m)
246
+ return null;
247
+ const n = Number.parseInt(m[1], 10);
248
+ return Number.isFinite(n) ? n : null;
249
+ }
250
+ // -----------------------------------------------------------------------------
251
+ // Apply (goes through normal state-transition code path)
252
+ // -----------------------------------------------------------------------------
253
+ /**
254
+ * Apply a transition through the same provider adapter layer that
255
+ * `prlt ticket move` uses. This is load-bearing: it ensures hooks,
256
+ * provider sync, and worktree cleanup all fire.
257
+ */
258
+ async function applyTransition(db, storage, transition) {
259
+ const ticket = await storage.getTicket(transition.ticketId);
260
+ if (!ticket) {
261
+ throw new Error(`Ticket ${transition.ticketId} vanished between scan and apply`);
262
+ }
263
+ const provider = resolveTicketProvider(ticket.id, ticket.projectId ?? transition.projectId, db, storage, ticket.metadata);
264
+ const result = await provider.moveTicket(ticket.id, transition.toState);
265
+ if (!result.success) {
266
+ throw new Error(result.error ?? 'moveTicket failed without an error message');
267
+ }
268
+ }
269
+ /**
270
+ * For tickets whose source of truth is an external provider (Linear,
271
+ * Jira, etc.), the local PMO board can still hold a mirror row. After
272
+ * successfully moving the external ticket, reconcile the local PMO side
273
+ * too so `prlt board` does not drift.
274
+ *
275
+ * Best effort — failures here never block the primary transition.
276
+ */
277
+ async function reconcilePmoMirror(db, storage, transition, log) {
278
+ try {
279
+ const mirror = await storage.getTicket(transition.ticketId);
280
+ if (!mirror)
281
+ return;
282
+ // Only move if the mirror truly holds a different status — that way
283
+ // we stay idempotent and do not fire redundant hooks.
284
+ if (mirror.statusName && mirror.statusName.toLowerCase() === transition.toState.toLowerCase()) {
285
+ return;
286
+ }
287
+ // Use the pmo provider directly (bypass resolver so we definitely
288
+ // hit the local mirror, not bounce back to Linear).
289
+ const pmoProvider = resolveTicketProvider(mirror.id, mirror.projectId ?? transition.projectId, db, storage,
290
+ // Strip external_source so the resolver picks PMO.
291
+ null);
292
+ if (pmoProvider.name !== 'pmo')
293
+ return;
294
+ const result = await pmoProvider.moveTicket(mirror.id, transition.toState);
295
+ if (!result.success && log) {
296
+ log(`[reconcile] pmo mirror update for ${mirror.id} failed: ${result.error}`);
297
+ }
298
+ }
299
+ catch (err) {
300
+ log?.(`[reconcile] pmo mirror update for ${transition.ticketId} errored: ` +
301
+ (err instanceof Error ? err.message : String(err)));
302
+ }
303
+ }
304
+ // -----------------------------------------------------------------------------
305
+ // Project / provider resolution helpers
306
+ // -----------------------------------------------------------------------------
307
+ async function listAllProjectIds(storage) {
308
+ try {
309
+ const projects = await storage.listProjects();
310
+ return projects.map(p => p.id);
311
+ }
312
+ catch {
313
+ return [];
314
+ }
315
+ }
316
+ /**
317
+ * Resolve the provider name for a ticket without triggering any state
318
+ * resolution or event emission. Used purely so the transition record
319
+ * can log which backend owns the ticket.
320
+ */
321
+ function providerNameFor(db, storage, ticket) {
322
+ try {
323
+ const provider = resolveTicketProvider(ticket.id, ticket.projectId ?? '', db, storage, ticket.metadata);
324
+ return provider.name;
325
+ }
326
+ catch {
327
+ return 'pmo';
328
+ }
329
+ }
330
+ function toReconcileTicket(ticket) {
331
+ return {
332
+ id: ticket.id,
333
+ title: ticket.title,
334
+ projectId: ticket.projectId,
335
+ statusName: ticket.statusName,
336
+ statusCategory: ticket.statusCategory,
337
+ branch: ticket.branch,
338
+ metadata: ticket.metadata,
339
+ };
340
+ }
341
+ // -----------------------------------------------------------------------------
342
+ // Watch loop
343
+ // -----------------------------------------------------------------------------
344
+ /**
345
+ * Run the reconciler on a timer until the caller-supplied `shouldStop`
346
+ * callback returns true.
347
+ *
348
+ * Each iteration runs one full {@link runReconcile} cycle and then sleeps
349
+ * for `intervalMs` before the next one. Errors inside a cycle are caught
350
+ * and surfaced via `log` — a single bad cycle never kills the watcher.
351
+ */
352
+ export async function watchReconcile(db, storage, options = {}) {
353
+ const { intervalMs = 5 * 60 * 1000, shouldStop = () => false, onCycle, sleep = ms => new Promise(resolve => setTimeout(resolve, ms)), log, } = options;
354
+ // Cycle counter for tests that want to bound the loop.
355
+ let cycle = 0;
356
+ while (!shouldStop()) {
357
+ try {
358
+ // eslint-disable-next-line no-await-in-loop
359
+ const report = await runReconcile(db, storage, options);
360
+ cycle += 1;
361
+ log?.(`[reconcile] cycle ${cycle} complete — checked=${report.checked} ` +
362
+ `applied=${report.applied.length} skipped=${report.skipped.length} ` +
363
+ `failed=${report.failed.length}`);
364
+ onCycle?.(report);
365
+ }
366
+ catch (err) {
367
+ log?.(`[reconcile] cycle failed: ${err instanceof Error ? err.message : String(err)}`);
368
+ }
369
+ if (shouldStop())
370
+ return;
371
+ // eslint-disable-next-line no-await-in-loop
372
+ await sleep(intervalMs);
373
+ }
374
+ }
375
+ // Exported for tests that want to call the PR URL parser directly.
376
+ export { parsePRNumberFromUrl as _parsePRNumberFromUrl };
377
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/reconcile/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Tier 2 State Reconciler — Types (PRLT-1280)
3
+ *
4
+ * The reconciler is the safety net that fixes board drift when Tier 1
5
+ * (event-driven hooks) misses an event. It polls the provider (GitHub for
6
+ * PR state) and fires normal ticket transitions when it detects drift.
7
+ *
8
+ * Design constraints:
9
+ * - Provider-agnostic: the reconciler core does not branch on provider type.
10
+ * It routes every transition through the existing provider adapter layer.
11
+ * - Idempotent: running twice back-to-back must be safe. Every check asks
12
+ * "is current == expected?" and no-ops when they match.
13
+ * - No LLM, no supervision tree, no daemon rewrite. One function on a timer.
14
+ */
15
+ import type { PRInfo } from '../pr/index.js';
16
+ import type { Ticket } from '../pmo/types.js';
17
+ /**
18
+ * Source of a linked PR. Used for logging and to decide which PMO mapping
19
+ * to touch when the reconciler sees a transition is needed.
20
+ */
21
+ export type LinkedPRSource = 'ticket_branch' | 'external_map';
22
+ /**
23
+ * A PR linked to a ticket, along with its provenance.
24
+ */
25
+ export interface LinkedPR {
26
+ /** Live PR info as reported by GitHub at the time of the reconcile cycle */
27
+ pr: PRInfo;
28
+ /** How this PR was discovered for the ticket */
29
+ source: LinkedPRSource;
30
+ }
31
+ /**
32
+ * A single transition the reconciler is about to apply (or would apply in dry-run).
33
+ */
34
+ export interface ReconcileTransition {
35
+ /** Ticket being moved */
36
+ ticketId: string;
37
+ /** Ticket display title (for logging) */
38
+ ticketTitle: string;
39
+ /** The project the ticket belongs to */
40
+ projectId: string;
41
+ /** Provider name that owns this ticket (from resolver) */
42
+ provider: string;
43
+ /** Ticket's state before the transition */
44
+ fromState: string | null;
45
+ /** Ticket's state after the transition (target column name) */
46
+ toState: string;
47
+ /** PR number that drove this transition, when available */
48
+ prNumber?: number;
49
+ /** PR state that drove this transition (MERGED, CLOSED, OPEN) */
50
+ prState?: PRInfo['state'];
51
+ /** Human-readable reason for the transition */
52
+ reason: string;
53
+ /** ISO timestamp the reconciler decided on this transition */
54
+ decidedAt: string;
55
+ }
56
+ /**
57
+ * Options for running a reconcile cycle.
58
+ */
59
+ export interface ReconcileOptions {
60
+ /** If true, compute transitions but do not apply them */
61
+ dryRun?: boolean;
62
+ /** Working directory for GitHub CLI calls (git remote, gh pr view) */
63
+ cwd?: string;
64
+ /** Optional callback for human-readable logs */
65
+ log?: (msg: string) => void;
66
+ /**
67
+ * Restrict reconciliation to a specific project. When omitted the
68
+ * reconciler runs workspace-wide across every project.
69
+ */
70
+ projectId?: string;
71
+ /**
72
+ * Injectable PR lookup. Tests use this to avoid the real gh CLI.
73
+ * Implementations must query live — the reconciler is not allowed to
74
+ * depend on a local PR cache.
75
+ */
76
+ prLookup?: PRLookupFn;
77
+ }
78
+ /**
79
+ * Function signature for live PR lookup.
80
+ *
81
+ * The reconciler calls this once per PR reference. Implementations must
82
+ * hit the remote GitHub API (gh CLI or the REST client) — a stale local
83
+ * cache defeats the whole point of a Tier 2 reconciler.
84
+ */
85
+ export type PRLookupFn = (ref: PRLookupRef, cwd?: string) => PRInfo | null;
86
+ /**
87
+ * A PR reference that can be resolved to a PRInfo.
88
+ *
89
+ * Tickets link to PRs via either their ticket branch (common for
90
+ * prlt-managed work) or via a stored URL in the external execution
91
+ * mapping table (common when the PR was opened outside prlt).
92
+ */
93
+ export type PRLookupRef = {
94
+ kind: 'branch';
95
+ branch: string;
96
+ } | {
97
+ kind: 'number';
98
+ number: number;
99
+ } | {
100
+ kind: 'url';
101
+ url: string;
102
+ };
103
+ /**
104
+ * Report returned after a reconcile cycle completes.
105
+ */
106
+ export interface ReconcileReport {
107
+ /** Total number of tickets considered (across all scanned projects) */
108
+ checked: number;
109
+ /** Transitions that were applied (or would be applied in dry-run) */
110
+ applied: ReconcileTransition[];
111
+ /** Transitions that the reconciler decided on but did not execute because of --dry-run */
112
+ skipped: ReconcileTransition[];
113
+ /** Transitions that failed to apply */
114
+ failed: Array<{
115
+ transition: ReconcileTransition;
116
+ error: string;
117
+ }>;
118
+ /** Non-fatal errors encountered while gathering state */
119
+ errors: Array<{
120
+ ticketId: string;
121
+ error: string;
122
+ }>;
123
+ /** ISO timestamp when the cycle started */
124
+ startedAt: string;
125
+ /** ISO timestamp when the cycle finished */
126
+ finishedAt: string;
127
+ }
128
+ /**
129
+ * Minimal view of a ticket the reconciler actually reads. Narrow surface
130
+ * makes the reconciler easy to unit-test without fabricating full Ticket
131
+ * objects.
132
+ */
133
+ export type ReconcileTicket = Pick<Ticket, 'id' | 'title' | 'projectId' | 'statusName' | 'statusCategory' | 'branch' | 'metadata'>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Tier 2 State Reconciler — Types (PRLT-1280)
3
+ *
4
+ * The reconciler is the safety net that fixes board drift when Tier 1
5
+ * (event-driven hooks) misses an event. It polls the provider (GitHub for
6
+ * PR state) and fires normal ticket transitions when it detects drift.
7
+ *
8
+ * Design constraints:
9
+ * - Provider-agnostic: the reconciler core does not branch on provider type.
10
+ * It routes every transition through the existing provider adapter layer.
11
+ * - Idempotent: running twice back-to-back must be safe. Every check asks
12
+ * "is current == expected?" and no-ops when they match.
13
+ * - No LLM, no supervision tree, no daemon rewrite. One function on a timer.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/reconcile/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}