@proletariat/cli 0.3.110 → 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.
- package/dist/commands/gateway/connect.d.ts +33 -0
- package/dist/commands/gateway/connect.js +130 -0
- package/dist/commands/gateway/connect.js.map +1 -0
- package/dist/commands/gateway/disconnect.d.ts +21 -0
- package/dist/commands/gateway/disconnect.js +69 -0
- package/dist/commands/gateway/disconnect.js.map +1 -0
- package/dist/commands/gateway/start.d.ts +23 -0
- package/dist/commands/gateway/start.js +133 -0
- package/dist/commands/gateway/start.js.map +1 -0
- package/dist/commands/gateway/status.d.ts +16 -0
- package/dist/commands/gateway/status.js +76 -0
- package/dist/commands/gateway/status.js.map +1 -0
- package/dist/commands/gateway/test.d.ts +22 -0
- package/dist/commands/gateway/test.js +83 -0
- package/dist/commands/gateway/test.js.map +1 -0
- package/dist/commands/orchestrator/attach.d.ts +2 -0
- package/dist/commands/orchestrator/attach.js +80 -118
- package/dist/commands/orchestrator/attach.js.map +1 -1
- package/dist/commands/orchestrator/start.js +21 -0
- package/dist/commands/orchestrator/start.js.map +1 -1
- package/dist/commands/orchestrator/status.d.ts +3 -0
- package/dist/commands/orchestrator/status.js +104 -130
- package/dist/commands/orchestrator/status.js.map +1 -1
- package/dist/commands/orchestrator/stop.d.ts +2 -0
- package/dist/commands/orchestrator/stop.js +105 -107
- package/dist/commands/orchestrator/stop.js.map +1 -1
- package/dist/commands/reconcile.d.ts +29 -0
- package/dist/commands/reconcile.js +140 -0
- package/dist/commands/reconcile.js.map +1 -0
- package/dist/commands/session/attach.d.ts +2 -6
- package/dist/commands/session/attach.js +68 -97
- package/dist/commands/session/attach.js.map +1 -1
- package/dist/commands/session/list.d.ts +4 -1
- package/dist/commands/session/list.js +160 -326
- package/dist/commands/session/list.js.map +1 -1
- package/dist/commands/work/ship.js +131 -61
- package/dist/commands/work/ship.js.map +1 -1
- package/dist/commands/work/start.js +104 -49
- package/dist/commands/work/start.js.map +1 -1
- package/dist/lib/execution/session-utils.d.ts +4 -1
- package/dist/lib/execution/session-utils.js +3 -0
- package/dist/lib/execution/session-utils.js.map +1 -1
- package/dist/lib/gateway/channel-factory.d.ts +13 -0
- package/dist/lib/gateway/channel-factory.js +37 -0
- package/dist/lib/gateway/channel-factory.js.map +1 -0
- package/dist/lib/gateway/channels/telegram.d.ts +115 -0
- package/dist/lib/gateway/channels/telegram.js +215 -0
- package/dist/lib/gateway/channels/telegram.js.map +1 -0
- package/dist/lib/gateway/router.d.ts +84 -0
- package/dist/lib/gateway/router.js +140 -0
- package/dist/lib/gateway/router.js.map +1 -0
- package/dist/lib/gateway/session-poker.d.ts +35 -0
- package/dist/lib/gateway/session-poker.js +85 -0
- package/dist/lib/gateway/session-poker.js.map +1 -0
- package/dist/lib/gateway/types.d.ts +124 -0
- package/dist/lib/gateway/types.js +17 -0
- package/dist/lib/gateway/types.js.map +1 -0
- package/dist/lib/machine-db-mirror.d.ts +64 -0
- package/dist/lib/machine-db-mirror.js +82 -0
- package/dist/lib/machine-db-mirror.js.map +1 -0
- package/dist/lib/machine-db.d.ts +98 -0
- package/dist/lib/machine-db.js +152 -0
- package/dist/lib/machine-db.js.map +1 -1
- package/dist/lib/orchestrate/prompt-chain.d.ts +19 -4
- package/dist/lib/orchestrate/prompt-chain.js +19 -4
- package/dist/lib/orchestrate/prompt-chain.js.map +1 -1
- package/dist/lib/pr/index.d.ts +34 -2
- package/dist/lib/pr/index.js +95 -4
- package/dist/lib/pr/index.js.map +1 -1
- package/dist/lib/reconcile/core.d.ts +62 -0
- package/dist/lib/reconcile/core.js +137 -0
- package/dist/lib/reconcile/core.js.map +1 -0
- package/dist/lib/reconcile/index.d.ts +54 -0
- package/dist/lib/reconcile/index.js +377 -0
- package/dist/lib/reconcile/index.js.map +1 -0
- package/dist/lib/reconcile/types.d.ts +133 -0
- package/dist/lib/reconcile/types.js +16 -0
- package/dist/lib/reconcile/types.js.map +1 -0
- package/dist/lib/session/renderer.d.ts +121 -0
- package/dist/lib/session/renderer.js +547 -0
- package/dist/lib/session/renderer.js.map +1 -0
- package/dist/lib/update-check.d.ts +64 -7
- package/dist/lib/update-check.js +164 -20
- package/dist/lib/update-check.js.map +1 -1
- package/oclif.manifest.json +1203 -750
- package/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier 2 State Reconciler — Pure core (PRLT-1280)
|
|
3
|
+
*
|
|
4
|
+
* This module holds the deterministic, provider-agnostic, side-effect-free
|
|
5
|
+
* logic of the reconciler. It answers a single question:
|
|
6
|
+
*
|
|
7
|
+
* Given a ticket and the live state of its linked PR(s), what target
|
|
8
|
+
* column should the ticket be in, and is that different from its
|
|
9
|
+
* current column?
|
|
10
|
+
*
|
|
11
|
+
* Splitting this out from the IO-heavy runner keeps the rules trivially
|
|
12
|
+
* unit-testable without spinning up a database or the gh CLI.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Derive the expected state for a ticket given its linked PRs.
|
|
16
|
+
*
|
|
17
|
+
* Rules (mirrors the Tier 2 spec in PRLT-1280):
|
|
18
|
+
* 1. Any linked PR is MERGED → ticket belongs in Done
|
|
19
|
+
* 2. Ticket is currently in "review" category AND every open PR has been
|
|
20
|
+
* CLOSED without merging → move back to In Progress so humans can
|
|
21
|
+
* resume work. (Rule 4 in the existing sync reconciler uses Backlog,
|
|
22
|
+
* but PRLT-1280 explicitly asks for "back to In Progress or a review
|
|
23
|
+
* queue" — In Progress matches the active work better than Backlog.)
|
|
24
|
+
* 3. Otherwise: no expected transition.
|
|
25
|
+
*
|
|
26
|
+
* `null` means "leave the ticket alone". The reconciler MUST treat `null`
|
|
27
|
+
* as the idempotent path — that's how "second run is a no-op" works.
|
|
28
|
+
*/
|
|
29
|
+
export function deriveExpectedState(ticket, linkedPRs, config) {
|
|
30
|
+
const category = ticket.statusCategory;
|
|
31
|
+
const doneStatus = config?.done ?? 'Done';
|
|
32
|
+
const inProgressStatus = config?.in_progress ?? 'In Progress';
|
|
33
|
+
// Terminal states are load-bearing: we never pull a ticket back out of
|
|
34
|
+
// Done or Canceled. A human may have marked it closed for a reason.
|
|
35
|
+
if (category === 'completed' || category === 'canceled') {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
// Rule 1: any merged PR wins. Even if another linked PR is still open,
|
|
39
|
+
// a merged PR means the ticket's work shipped.
|
|
40
|
+
const merged = linkedPRs.find(lp => lp.pr.state === 'MERGED');
|
|
41
|
+
if (merged) {
|
|
42
|
+
// Idempotency: if the ticket is already sitting in the done column
|
|
43
|
+
// name we resolved, there is nothing to do.
|
|
44
|
+
if (statusMatches(ticket.statusName, doneStatus)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
targetState: doneStatus,
|
|
49
|
+
reason: `GitHub PR #${merged.pr.number} is MERGED (merged upstream), ` +
|
|
50
|
+
`expected ${doneStatus}, was ${ticket.statusName ?? category ?? 'unknown'}`,
|
|
51
|
+
driver: merged,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Rule 2: ticket is in review-land, but every linked PR is closed without
|
|
55
|
+
// merging. Pull it back to In Progress so it gets picked up again.
|
|
56
|
+
const hasLinkedPRs = linkedPRs.length > 0;
|
|
57
|
+
const allClosedNoMerge = hasLinkedPRs && linkedPRs.every(lp => lp.pr.state === 'CLOSED');
|
|
58
|
+
const inReview = category === 'started' && isReviewStatus(ticket.statusName, config);
|
|
59
|
+
if (allClosedNoMerge && inReview) {
|
|
60
|
+
if (statusMatches(ticket.statusName, inProgressStatus)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const closed = linkedPRs[0];
|
|
64
|
+
return {
|
|
65
|
+
targetState: inProgressStatus,
|
|
66
|
+
reason: `GitHub PR #${closed.pr.number} is CLOSED without merging, ` +
|
|
67
|
+
`expected ${inProgressStatus}, was ${ticket.statusName ?? category ?? 'unknown'}`,
|
|
68
|
+
driver: closed,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build a {@link ReconcileTransition} record from a derived expected-state
|
|
75
|
+
* decision. Factored out so the runner and the tests both produce the
|
|
76
|
+
* same transition shape.
|
|
77
|
+
*/
|
|
78
|
+
export function buildTransition(params) {
|
|
79
|
+
const { ticket, provider, targetState, reason, driver, now } = params;
|
|
80
|
+
return {
|
|
81
|
+
ticketId: ticket.id,
|
|
82
|
+
ticketTitle: ticket.title ?? '',
|
|
83
|
+
projectId: ticket.projectId ?? '',
|
|
84
|
+
provider,
|
|
85
|
+
fromState: ticket.statusName ?? null,
|
|
86
|
+
toState: targetState,
|
|
87
|
+
prNumber: driver?.pr.number,
|
|
88
|
+
prState: driver?.pr.state,
|
|
89
|
+
reason,
|
|
90
|
+
decidedAt: (now ?? new Date()).toISOString(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Format a transition for the structured log line required by the AC.
|
|
95
|
+
* The format is stable so scripts and tests can grep on it.
|
|
96
|
+
*
|
|
97
|
+
* Example:
|
|
98
|
+
* [reconcile] TKT-142: In Review → Done | PR #321 MERGED @ 2026-04-08T00:00:00Z | reason: ...
|
|
99
|
+
*/
|
|
100
|
+
export function formatTransitionLogLine(t) {
|
|
101
|
+
const prPart = t.prNumber !== undefined
|
|
102
|
+
? `PR #${t.prNumber} ${t.prState ?? ''}`.trim() + ` @ ${t.decidedAt}`
|
|
103
|
+
: `no PR @ ${t.decidedAt}`;
|
|
104
|
+
const from = t.fromState ?? 'unknown';
|
|
105
|
+
return `[reconcile] ${t.ticketId}: ${from} → ${t.toState} | ${prPart} | reason: ${t.reason}`;
|
|
106
|
+
}
|
|
107
|
+
// -----------------------------------------------------------------------------
|
|
108
|
+
// helpers
|
|
109
|
+
// -----------------------------------------------------------------------------
|
|
110
|
+
function statusMatches(current, target) {
|
|
111
|
+
if (!current)
|
|
112
|
+
return false;
|
|
113
|
+
return current.toLowerCase() === target.toLowerCase();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Match the existing sync reconciler's notion of "review status" so the
|
|
117
|
+
* Tier 2 reconciler treats the same columns as review that Tier 1 does.
|
|
118
|
+
* When a WorkflowConfig is provided, matches exactly against the
|
|
119
|
+
* configured review column; otherwise falls back to substring matching.
|
|
120
|
+
*/
|
|
121
|
+
function isReviewStatus(statusName, config) {
|
|
122
|
+
const name = (statusName ?? '').toLowerCase();
|
|
123
|
+
if (!name)
|
|
124
|
+
return false;
|
|
125
|
+
if (config?.review) {
|
|
126
|
+
return name === config.review.toLowerCase();
|
|
127
|
+
}
|
|
128
|
+
return name.includes('review');
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Re-export for callers that want a direct PRInfo tuple without the
|
|
132
|
+
* LinkedPR wrapper.
|
|
133
|
+
*/
|
|
134
|
+
export function toLinkedPR(pr, source) {
|
|
135
|
+
return { pr, source };
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../../src/lib/reconcile/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAuB,EACvB,SAAqB,EACrB,MAAuB;IAEvB,MAAM,QAAQ,GAAG,MAAM,CAAC,cAAc,CAAA;IACtC,MAAM,UAAU,GAAG,MAAM,EAAE,IAAI,IAAI,MAAM,CAAA;IACzC,MAAM,gBAAgB,GAAG,MAAM,EAAE,WAAW,IAAI,aAAa,CAAA;IAE7D,uEAAuE;IACvE,oEAAoE;IACpE,IAAI,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;QACxD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,uEAAuE;IACvE,+CAA+C;IAC/C,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAA;IAC7D,IAAI,MAAM,EAAE,CAAC;QACX,mEAAmE;QACnE,4CAA4C;QAC5C,IAAI,aAAa,CAAC,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO;YACL,WAAW,EAAE,UAAU;YACvB,MAAM,EACJ,cAAc,MAAM,CAAC,EAAE,CAAC,MAAM,gCAAgC;gBAC9D,YAAY,UAAU,SAAS,MAAM,CAAC,UAAU,IAAI,QAAQ,IAAI,SAAS,EAAE;YAC7E,MAAM,EAAE,MAAM;SACf,CAAA;IACH,CAAC;IAED,0EAA0E;IAC1E,mEAAmE;IACnE,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAA;IACzC,MAAM,gBAAgB,GACpB,YAAY,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAA;IACjE,MAAM,QAAQ,GAAG,QAAQ,KAAK,SAAS,IAAI,cAAc,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IAEpF,IAAI,gBAAgB,IAAI,QAAQ,EAAE,CAAC;QACjC,IAAI,aAAa,CAAC,MAAM,CAAC,UAAU,EAAE,gBAAgB,CAAC,EAAE,CAAC;YACvD,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;QAC3B,OAAO;YACL,WAAW,EAAE,gBAAgB;YAC7B,MAAM,EACJ,cAAc,MAAM,CAAC,EAAE,CAAC,MAAM,8BAA8B;gBAC5D,YAAY,gBAAgB,SAAS,MAAM,CAAC,UAAU,IAAI,QAAQ,IAAI,SAAS,EAAE;YACnF,MAAM,EAAE,MAAM;SACf,CAAA;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAO/B;IACC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAA;IACrE,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,EAAE;QACnB,WAAW,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE;QAC/B,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,EAAE;QACjC,QAAQ;QACR,SAAS,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI;QACpC,OAAO,EAAE,WAAW;QACpB,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM;QAC3B,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK;QACzB,MAAM;QACN,SAAS,EAAE,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE;KAC7C,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,CAAsB;IAC5D,MAAM,MAAM,GACV,CAAC,CAAC,QAAQ,KAAK,SAAS;QACtB,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,SAAS,EAAE;QACrE,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,EAAE,CAAA;IAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,IAAI,SAAS,CAAA;IACrC,OAAO,eAAe,CAAC,CAAC,QAAQ,KAAK,IAAI,MAAM,CAAC,CAAC,OAAO,MAAM,MAAM,cAAc,CAAC,CAAC,MAAM,EAAE,CAAA;AAC9F,CAAC;AAED,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAEhF,SAAS,aAAa,CAAC,OAAkC,EAAE,MAAc;IACvE,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAC1B,OAAO,OAAO,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,WAAW,EAAE,CAAA;AACvD,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CACrB,UAAqC,EACrC,MAAuB;IAEvB,MAAM,IAAI,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IAC7C,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IACvB,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAA;IAC7C,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,EAAU,EAAE,MAA0B;IAC/D,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,CAAA;AACvB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
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 type Database from 'better-sqlite3';
|
|
17
|
+
import type { PMOStorage } from '../pmo/types.js';
|
|
18
|
+
import type { ProviderStorage } from '../providers/types.js';
|
|
19
|
+
import type { PRLookupFn, ReconcileOptions, ReconcileReport } from './types.js';
|
|
20
|
+
export type { LinkedPR, LinkedPRSource, PRLookupFn, PRLookupRef, ReconcileOptions, ReconcileReport, ReconcileTicket, ReconcileTransition, } from './types.js';
|
|
21
|
+
export { deriveExpectedState, buildTransition, formatTransitionLogLine } from './core.js';
|
|
22
|
+
/**
|
|
23
|
+
* Default live PR lookup. Always goes to the remote — the Tier 2
|
|
24
|
+
* reconciler must not depend on any local PR cache.
|
|
25
|
+
*/
|
|
26
|
+
export declare const defaultPRLookup: PRLookupFn;
|
|
27
|
+
/**
|
|
28
|
+
* Run one reconciliation cycle.
|
|
29
|
+
*
|
|
30
|
+
* Strictly idempotent: if a ticket is already in its expected state the
|
|
31
|
+
* runner computes no transition and touches nothing. Running twice
|
|
32
|
+
* back-to-back produces no redundant side effects.
|
|
33
|
+
*/
|
|
34
|
+
export declare function runReconcile(db: Database.Database, storage: PMOStorage & ProviderStorage, options?: ReconcileOptions): Promise<ReconcileReport>;
|
|
35
|
+
/**
|
|
36
|
+
* Parse a PR number out of a github.com URL.
|
|
37
|
+
* Accepts both `/pull/123` and `/pull/123/...` shapes.
|
|
38
|
+
*/
|
|
39
|
+
declare function parsePRNumberFromUrl(url: string): number | null;
|
|
40
|
+
/**
|
|
41
|
+
* Run the reconciler on a timer until the caller-supplied `shouldStop`
|
|
42
|
+
* callback returns true.
|
|
43
|
+
*
|
|
44
|
+
* Each iteration runs one full {@link runReconcile} cycle and then sleeps
|
|
45
|
+
* for `intervalMs` before the next one. Errors inside a cycle are caught
|
|
46
|
+
* and surfaced via `log` — a single bad cycle never kills the watcher.
|
|
47
|
+
*/
|
|
48
|
+
export declare function watchReconcile(db: Database.Database, storage: PMOStorage & ProviderStorage, options?: ReconcileOptions & {
|
|
49
|
+
intervalMs?: number;
|
|
50
|
+
shouldStop?: () => boolean;
|
|
51
|
+
onCycle?: (report: ReconcileReport) => void;
|
|
52
|
+
sleep?: (ms: number) => Promise<void>;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
export { parsePRNumberFromUrl as _parsePRNumberFromUrl };
|
|
@@ -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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"}
|