@loopover/miner 0.1.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.
- package/DEPLOYMENT.md +156 -0
- package/Dockerfile +38 -0
- package/README.md +269 -0
- package/bin/gittensory-miner-mcp.d.ts +64 -0
- package/bin/gittensory-miner-mcp.js +279 -0
- package/bin/gittensory-miner.js +196 -0
- package/docs/coding-agent-driver.md +140 -0
- package/docs/config-precedence.md +96 -0
- package/docs/cross-repo-discovery-phase1.md +55 -0
- package/docs/discovery-index-contract.md +60 -0
- package/docs/discovery-plane-operator-guide.md +101 -0
- package/docs/env-reference.md +30 -0
- package/docs/fleet-run-manifest.md +50 -0
- package/docs/miner-goal-spec.md +76 -0
- package/docs/miner-selfimprove-calibration.md +105 -0
- package/docs/observability.md +60 -0
- package/docs/operations-runbook.md +209 -0
- package/docs/repo-agnostic-capability-audit.md +129 -0
- package/docs/unattended-scheduling.md +107 -0
- package/expected-engine.version +1 -0
- package/lib/ams-policy.d.ts +20 -0
- package/lib/ams-policy.js +68 -0
- package/lib/attempt-cli.d.ts +99 -0
- package/lib/attempt-cli.js +523 -0
- package/lib/attempt-input-builder.d.ts +24 -0
- package/lib/attempt-input-builder.js +91 -0
- package/lib/attempt-log.d.ts +37 -0
- package/lib/attempt-log.js +177 -0
- package/lib/attempt-runner.d.ts +59 -0
- package/lib/attempt-runner.js +164 -0
- package/lib/attempt-worktree.d.ts +31 -0
- package/lib/attempt-worktree.js +94 -0
- package/lib/calibration-cli.d.ts +1 -0
- package/lib/calibration-cli.js +92 -0
- package/lib/calibration-run.d.ts +161 -0
- package/lib/calibration-run.js +232 -0
- package/lib/calibration-types.d.ts +46 -0
- package/lib/calibration-types.js +74 -0
- package/lib/calibration.d.ts +24 -0
- package/lib/calibration.js +99 -0
- package/lib/ci-poller.d.ts +33 -0
- package/lib/ci-poller.js +234 -0
- package/lib/claim-adjudication.d.ts +21 -0
- package/lib/claim-adjudication.js +36 -0
- package/lib/claim-conflict-resolver.d.ts +29 -0
- package/lib/claim-conflict-resolver.js +101 -0
- package/lib/claim-ledger-cli.d.ts +59 -0
- package/lib/claim-ledger-cli.js +320 -0
- package/lib/claim-ledger-expiry.d.ts +20 -0
- package/lib/claim-ledger-expiry.js +41 -0
- package/lib/claim-ledger.d.ts +63 -0
- package/lib/claim-ledger.js +303 -0
- package/lib/cli-error.d.ts +3 -0
- package/lib/cli-error.js +27 -0
- package/lib/cli.d.ts +3 -0
- package/lib/cli.js +72 -0
- package/lib/coding-agent-construction.d.ts +17 -0
- package/lib/coding-agent-construction.js +103 -0
- package/lib/coding-agent-house-rules.d.ts +24 -0
- package/lib/coding-agent-house-rules.js +64 -0
- package/lib/coding-task-spec.d.ts +44 -0
- package/lib/coding-task-spec.js +183 -0
- package/lib/config-precedence.d.ts +25 -0
- package/lib/config-precedence.js +38 -0
- package/lib/deny-check.d.ts +11 -0
- package/lib/deny-check.js +76 -0
- package/lib/deny-hook-synthesis.d.ts +93 -0
- package/lib/deny-hook-synthesis.js +399 -0
- package/lib/deny-hooks.d.ts +28 -0
- package/lib/deny-hooks.js +155 -0
- package/lib/deployment-docs-audit.d.ts +41 -0
- package/lib/deployment-docs-audit.js +115 -0
- package/lib/discover-cli.d.ts +94 -0
- package/lib/discover-cli.js +284 -0
- package/lib/discovery-throttle.d.ts +8 -0
- package/lib/discovery-throttle.js +33 -0
- package/lib/env-file-indirection.d.ts +4 -0
- package/lib/env-file-indirection.js +45 -0
- package/lib/event-ledger-cli.d.ts +82 -0
- package/lib/event-ledger-cli.js +266 -0
- package/lib/event-ledger.d.ts +37 -0
- package/lib/event-ledger.js +210 -0
- package/lib/execute-local-write.d.ts +14 -0
- package/lib/execute-local-write.js +50 -0
- package/lib/feasibility-cli.d.ts +25 -0
- package/lib/feasibility-cli.js +80 -0
- package/lib/forge-config.d.ts +17 -0
- package/lib/forge-config.js +37 -0
- package/lib/governor-action-mode.d.ts +26 -0
- package/lib/governor-action-mode.js +47 -0
- package/lib/governor-chokepoint-persisted.d.ts +18 -0
- package/lib/governor-chokepoint-persisted.js +46 -0
- package/lib/governor-chokepoint.d.ts +14 -0
- package/lib/governor-chokepoint.js +52 -0
- package/lib/governor-kill-switch.d.ts +26 -0
- package/lib/governor-kill-switch.js +47 -0
- package/lib/governor-ledger-cli.d.ts +32 -0
- package/lib/governor-ledger-cli.js +155 -0
- package/lib/governor-ledger.d.ts +46 -0
- package/lib/governor-ledger.js +203 -0
- package/lib/governor-open-pr.d.ts +21 -0
- package/lib/governor-open-pr.js +27 -0
- package/lib/governor-pause-cli.d.ts +23 -0
- package/lib/governor-pause-cli.js +171 -0
- package/lib/governor-run-halt.d.ts +37 -0
- package/lib/governor-run-halt.js +58 -0
- package/lib/governor-state.d.ts +63 -0
- package/lib/governor-state.js +363 -0
- package/lib/governor-write-rate-limit.d.ts +30 -0
- package/lib/governor-write-rate-limit.js +64 -0
- package/lib/harness-submission-trigger.d.ts +69 -0
- package/lib/harness-submission-trigger.js +138 -0
- package/lib/http-retry.d.ts +12 -0
- package/lib/http-retry.js +52 -0
- package/lib/laptop-init.d.ts +53 -0
- package/lib/laptop-init.js +332 -0
- package/lib/live-issue-snapshot.d.ts +16 -0
- package/lib/live-issue-snapshot.js +117 -0
- package/lib/local-store.d.ts +18 -0
- package/lib/local-store.js +62 -0
- package/lib/logger.d.ts +58 -0
- package/lib/logger.js +167 -0
- package/lib/loop-cli.d.ts +66 -0
- package/lib/loop-cli.js +540 -0
- package/lib/loop-closure.d.ts +35 -0
- package/lib/loop-closure.js +66 -0
- package/lib/loop-reentry.d.ts +51 -0
- package/lib/loop-reentry.js +124 -0
- package/lib/manage-poll.d.ts +79 -0
- package/lib/manage-poll.js +254 -0
- package/lib/manage-status.d.ts +70 -0
- package/lib/manage-status.js +241 -0
- package/lib/metrics-cli.d.ts +9 -0
- package/lib/metrics-cli.js +50 -0
- package/lib/migrate-cli.d.ts +24 -0
- package/lib/migrate-cli.js +111 -0
- package/lib/miner-goal-spec.d.ts +12 -0
- package/lib/miner-goal-spec.js +67 -0
- package/lib/opportunity-fanout.d.ts +86 -0
- package/lib/opportunity-fanout.js +597 -0
- package/lib/opportunity-ranker.d.ts +36 -0
- package/lib/opportunity-ranker.js +118 -0
- package/lib/orb-export.d.ts +54 -0
- package/lib/orb-export.js +206 -0
- package/lib/plan-store-cli.d.ts +37 -0
- package/lib/plan-store-cli.js +151 -0
- package/lib/plan-store.d.ts +51 -0
- package/lib/plan-store.js +231 -0
- package/lib/policy-doc-cache.d.ts +25 -0
- package/lib/policy-doc-cache.js +79 -0
- package/lib/policy-verdict-cache.d.ts +35 -0
- package/lib/policy-verdict-cache.js +106 -0
- package/lib/portfolio-dashboard.d.ts +30 -0
- package/lib/portfolio-dashboard.js +105 -0
- package/lib/portfolio-discovery.d.ts +29 -0
- package/lib/portfolio-discovery.js +100 -0
- package/lib/portfolio-queue-cli.d.ts +87 -0
- package/lib/portfolio-queue-cli.js +509 -0
- package/lib/portfolio-queue-expiry.d.ts +20 -0
- package/lib/portfolio-queue-expiry.js +51 -0
- package/lib/portfolio-queue-manager.d.ts +50 -0
- package/lib/portfolio-queue-manager.js +134 -0
- package/lib/portfolio-queue.d.ts +62 -0
- package/lib/portfolio-queue.js +351 -0
- package/lib/pr-disposition-poller.d.ts +26 -0
- package/lib/pr-disposition-poller.js +163 -0
- package/lib/pr-number-parse.d.ts +4 -0
- package/lib/pr-number-parse.js +23 -0
- package/lib/pr-outcome.d.ts +41 -0
- package/lib/pr-outcome.js +90 -0
- package/lib/prediction-ledger.d.ts +47 -0
- package/lib/prediction-ledger.js +222 -0
- package/lib/pretooluse-hook.d.ts +31 -0
- package/lib/pretooluse-hook.js +93 -0
- package/lib/process-lifecycle.d.ts +31 -0
- package/lib/process-lifecycle.js +106 -0
- package/lib/purge-cli.d.ts +44 -0
- package/lib/purge-cli.js +198 -0
- package/lib/rejection-signal.d.ts +6 -0
- package/lib/rejection-signal.js +101 -0
- package/lib/rejection-state-machine.d.ts +37 -0
- package/lib/rejection-state-machine.js +81 -0
- package/lib/rejection-templates.d.ts +12 -0
- package/lib/rejection-templates.js +71 -0
- package/lib/replay-objective-anchor.d.ts +79 -0
- package/lib/replay-objective-anchor.js +179 -0
- package/lib/replay-snapshot.d.ts +41 -0
- package/lib/replay-snapshot.js +288 -0
- package/lib/replay-task-generation.d.ts +126 -0
- package/lib/replay-task-generation.js +223 -0
- package/lib/repo-clone.d.ts +19 -0
- package/lib/repo-clone.js +102 -0
- package/lib/run-state-cli.d.ts +27 -0
- package/lib/run-state-cli.js +154 -0
- package/lib/run-state.d.ts +37 -0
- package/lib/run-state.js +150 -0
- package/lib/schema-version.d.ts +17 -0
- package/lib/schema-version.js +71 -0
- package/lib/self-review-context.d.ts +28 -0
- package/lib/self-review-context.js +339 -0
- package/lib/slop-assessment.d.ts +3 -0
- package/lib/slop-assessment.js +20 -0
- package/lib/stack-detection.d.ts +41 -0
- package/lib/stack-detection.js +248 -0
- package/lib/status.d.ts +64 -0
- package/lib/status.js +441 -0
- package/lib/store-maintenance.d.ts +31 -0
- package/lib/store-maintenance.js +183 -0
- package/lib/submission-freshness-check.d.ts +32 -0
- package/lib/submission-freshness-check.js +93 -0
- package/lib/update-check.d.ts +36 -0
- package/lib/update-check.js +161 -0
- package/lib/version.d.ts +3 -0
- package/lib/version.js +10 -0
- package/lib/worktree-allocator.d.ts +39 -0
- package/lib/worktree-allocator.js +262 -0
- package/package.json +50 -0
- package/schema/miner-goal-spec.schema.json +111 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { initEventLedger } from "./event-ledger.js";
|
|
2
|
+
import { initPortfolioQueueStore } from "./portfolio-queue.js";
|
|
3
|
+
import { initRunStateStore } from "./run-state.js";
|
|
4
|
+
import { argsWantJson, reportCliFailure } from "./cli-error.js";
|
|
5
|
+
|
|
6
|
+
/** Event vocabulary for manage-phase PR snapshots written by manage poll. (#2325) */
|
|
7
|
+
export const MANAGE_PR_UPDATE_EVENT = "manage_pr_update";
|
|
8
|
+
export const MANAGED_PR_IDENTIFIER_PREFIX = "pr:";
|
|
9
|
+
|
|
10
|
+
export function parseManagedPrIdentifier(identifier) {
|
|
11
|
+
if (typeof identifier !== "string") return null;
|
|
12
|
+
const match = identifier.match(/^pr:(\d+)$/);
|
|
13
|
+
if (!match) return null;
|
|
14
|
+
const prNumber = Number(match[1]);
|
|
15
|
+
return Number.isInteger(prNumber) && prNumber > 0 ? prNumber : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function formatManagedPrIdentifier(prNumber) {
|
|
19
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0) throw new Error("invalid_pr_number");
|
|
20
|
+
return `${MANAGED_PR_IDENTIFIER_PREFIX}${prNumber}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function optionalString(value) {
|
|
24
|
+
if (value === undefined || value === null) return null;
|
|
25
|
+
if (typeof value !== "string") return null;
|
|
26
|
+
const trimmed = value.trim();
|
|
27
|
+
return trimmed || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeManageUpdatePayload(payload) {
|
|
31
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
|
|
32
|
+
if (!Number.isInteger(payload.prNumber) || payload.prNumber <= 0) return null;
|
|
33
|
+
return {
|
|
34
|
+
prNumber: payload.prNumber,
|
|
35
|
+
branch: optionalString(payload.branch),
|
|
36
|
+
ciState: optionalString(payload.ciState),
|
|
37
|
+
gateVerdict: optionalString(payload.gateVerdict),
|
|
38
|
+
outcome: optionalString(payload.outcome),
|
|
39
|
+
lastPolledAt: optionalString(payload.lastPolledAt),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Index the latest manage snapshot per repo/PR from ascending ledger events. Pure. */
|
|
44
|
+
export function indexLatestManageUpdates(events) {
|
|
45
|
+
const latest = new Map();
|
|
46
|
+
for (const event of Array.isArray(events) ? events : []) {
|
|
47
|
+
if (event?.type !== MANAGE_PR_UPDATE_EVENT) continue;
|
|
48
|
+
if (typeof event.repoFullName !== "string" || !event.repoFullName.trim()) continue;
|
|
49
|
+
const normalized = normalizeManageUpdatePayload(event.payload);
|
|
50
|
+
if (!normalized) continue;
|
|
51
|
+
const key = `${event.repoFullName}:${normalized.prNumber}`;
|
|
52
|
+
latest.set(key, { ...normalized, repoFullName: event.repoFullName });
|
|
53
|
+
}
|
|
54
|
+
return latest;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Aggregate managed PR rows from the local portfolio queue and append-only event ledger. Read-only — never calls
|
|
59
|
+
* GitHub or mutates local stores. (#2325)
|
|
60
|
+
*/
|
|
61
|
+
export function collectManageStatus(sources) {
|
|
62
|
+
const portfolioQueue = sources?.portfolioQueue;
|
|
63
|
+
const eventLedger = sources?.eventLedger;
|
|
64
|
+
if (!portfolioQueue || typeof portfolioQueue.listQueue !== "function") {
|
|
65
|
+
throw new Error("invalid_portfolio_queue");
|
|
66
|
+
}
|
|
67
|
+
if (!eventLedger || typeof eventLedger.readEvents !== "function") {
|
|
68
|
+
throw new Error("invalid_event_ledger");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const rowsByKey = new Map();
|
|
72
|
+
for (const entry of portfolioQueue.listQueue(null)) {
|
|
73
|
+
const prNumber = parseManagedPrIdentifier(entry.identifier);
|
|
74
|
+
if (prNumber === null) continue;
|
|
75
|
+
const key = `${entry.repoFullName}:${prNumber}`;
|
|
76
|
+
rowsByKey.set(key, {
|
|
77
|
+
repoFullName: entry.repoFullName,
|
|
78
|
+
prNumber,
|
|
79
|
+
branch: null,
|
|
80
|
+
ciState: null,
|
|
81
|
+
gateVerdict: null,
|
|
82
|
+
outcome: null,
|
|
83
|
+
lastPolledAt: null,
|
|
84
|
+
queueStatus: entry.status,
|
|
85
|
+
priority: entry.priority,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const [key, update] of indexLatestManageUpdates(eventLedger.readEvents())) {
|
|
90
|
+
const existing = rowsByKey.get(key);
|
|
91
|
+
rowsByKey.set(key, {
|
|
92
|
+
repoFullName: update.repoFullName,
|
|
93
|
+
prNumber: update.prNumber,
|
|
94
|
+
branch: update.branch,
|
|
95
|
+
ciState: update.ciState,
|
|
96
|
+
gateVerdict: update.gateVerdict,
|
|
97
|
+
outcome: update.outcome,
|
|
98
|
+
lastPolledAt: update.lastPolledAt,
|
|
99
|
+
queueStatus: existing?.queueStatus ?? null,
|
|
100
|
+
priority: existing?.priority ?? null,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return [...rowsByKey.values()].sort((left, right) => {
|
|
105
|
+
const repoCmp = left.repoFullName.localeCompare(right.repoFullName);
|
|
106
|
+
if (repoCmp !== 0) return repoCmp;
|
|
107
|
+
return left.prNumber - right.prNumber;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Fold each tracked repo's current discover/plan/prepare run state alongside its managed PR rows into one
|
|
113
|
+
* "run portfolio" row per repo (#4279). `collectManageStatus` alone is PR-scoped only and never surfaces the
|
|
114
|
+
* run-state signal, so a repo actively discovering/planning with zero PRs yet is otherwise invisible. A repo
|
|
115
|
+
* appears here if it has EITHER a recorded run state OR at least one managed PR row.
|
|
116
|
+
*/
|
|
117
|
+
export function collectRunPortfolio(sources) {
|
|
118
|
+
const runStateStore = sources?.runStateStore;
|
|
119
|
+
if (!runStateStore || typeof runStateStore.listRunStates !== "function") {
|
|
120
|
+
throw new Error("invalid_run_state_store");
|
|
121
|
+
}
|
|
122
|
+
const prsByRepo = new Map();
|
|
123
|
+
for (const row of collectManageStatus(sources)) {
|
|
124
|
+
const list = prsByRepo.get(row.repoFullName) ?? [];
|
|
125
|
+
list.push(row);
|
|
126
|
+
prsByRepo.set(row.repoFullName, list);
|
|
127
|
+
}
|
|
128
|
+
// NOTE (#5563): keyed by repoFullName alone, not apiBaseUrl -- this dashboard fold predates multi-forge run
|
|
129
|
+
// states and produces exactly ONE row per repo name. If the same repo name has a recorded run state on two
|
|
130
|
+
// different hosts, only one (the later entry in listRunStates' order) survives here; the other's row is still
|
|
131
|
+
// intact in the store, just not surfaced in this particular view. Safe (no data loss, no write), just a display
|
|
132
|
+
// limitation -- broadening this fold to be host-aware is a separate, larger dashboard-shape change.
|
|
133
|
+
const runStateByRepo = new Map(runStateStore.listRunStates().map((entry) => [entry.repoFullName, entry]));
|
|
134
|
+
|
|
135
|
+
const repoFullNames = new Set([...prsByRepo.keys(), ...runStateByRepo.keys()]);
|
|
136
|
+
return [...repoFullNames].sort((left, right) => left.localeCompare(right)).map((repoFullName) => {
|
|
137
|
+
const prs = prsByRepo.get(repoFullName) ?? [];
|
|
138
|
+
const runState = runStateByRepo.get(repoFullName);
|
|
139
|
+
return {
|
|
140
|
+
repoFullName,
|
|
141
|
+
runState: runState?.state ?? null,
|
|
142
|
+
runStateUpdatedAt: runState?.updatedAt ?? null,
|
|
143
|
+
prCount: prs.length,
|
|
144
|
+
prs,
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function display(value) {
|
|
150
|
+
if (value === null || value === undefined) return "-";
|
|
151
|
+
return String(value);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function renderManageStatusTable(rows) {
|
|
155
|
+
if (!Array.isArray(rows) || rows.length === 0) return "no managed pull requests";
|
|
156
|
+
const header = [
|
|
157
|
+
"repo".padEnd(24),
|
|
158
|
+
"pr".padStart(4),
|
|
159
|
+
"branch".padEnd(16),
|
|
160
|
+
"ci".padEnd(10),
|
|
161
|
+
"gate".padEnd(10),
|
|
162
|
+
"outcome".padEnd(10),
|
|
163
|
+
"last-polled".padEnd(20),
|
|
164
|
+
"queue".padEnd(12),
|
|
165
|
+
"pri".padStart(4),
|
|
166
|
+
].join(" ");
|
|
167
|
+
const lines = rows.map((row) =>
|
|
168
|
+
[
|
|
169
|
+
row.repoFullName.padEnd(24),
|
|
170
|
+
String(row.prNumber).padStart(4),
|
|
171
|
+
display(row.branch).padEnd(16),
|
|
172
|
+
display(row.ciState).padEnd(10),
|
|
173
|
+
display(row.gateVerdict).padEnd(10),
|
|
174
|
+
display(row.outcome).padEnd(10),
|
|
175
|
+
display(row.lastPolledAt).padEnd(20),
|
|
176
|
+
display(row.queueStatus).padEnd(12),
|
|
177
|
+
display(row.priority).padStart(4),
|
|
178
|
+
].join(" "),
|
|
179
|
+
);
|
|
180
|
+
return [header, ...lines].join("\n");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** One row per tracked repo (run state + PR count), the compact companion to {@link renderManageStatusTable}'s
|
|
184
|
+
* per-PR detail (#4279). */
|
|
185
|
+
export function renderRunPortfolioTable(portfolio) {
|
|
186
|
+
if (!Array.isArray(portfolio) || portfolio.length === 0) return "no tracked repos";
|
|
187
|
+
const header = [
|
|
188
|
+
"repo".padEnd(24),
|
|
189
|
+
"run-state".padEnd(12),
|
|
190
|
+
"updated".padEnd(20),
|
|
191
|
+
"prs".padStart(4),
|
|
192
|
+
].join(" ");
|
|
193
|
+
const lines = portfolio.map((entry) =>
|
|
194
|
+
[
|
|
195
|
+
entry.repoFullName.padEnd(24),
|
|
196
|
+
display(entry.runState).padEnd(12),
|
|
197
|
+
display(entry.runStateUpdatedAt).padEnd(20),
|
|
198
|
+
String(entry.prCount).padStart(4),
|
|
199
|
+
].join(" "),
|
|
200
|
+
);
|
|
201
|
+
return [header, ...lines].join("\n");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function parseManageStatusArgs(args = []) {
|
|
205
|
+
for (const token of args) {
|
|
206
|
+
if (token === "--json") continue;
|
|
207
|
+
if (token.startsWith("-")) return { error: `Unknown option: ${token}` };
|
|
208
|
+
return { error: "Usage: gittensory-miner manage status [--json]" };
|
|
209
|
+
}
|
|
210
|
+
return { json: args.includes("--json") };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function runManageStatus(args = [], options = {}) {
|
|
214
|
+
const parsed = parseManageStatusArgs(args);
|
|
215
|
+
if ("error" in parsed) {
|
|
216
|
+
return reportCliFailure(argsWantJson(args), parsed.error);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const ownsPortfolioQueue = options.initPortfolioQueue === undefined;
|
|
220
|
+
const ownsEventLedger = options.initEventLedger === undefined;
|
|
221
|
+
const ownsRunStateStore = options.initRunStateStore === undefined;
|
|
222
|
+
const portfolioQueue = (options.initPortfolioQueue ?? initPortfolioQueueStore)();
|
|
223
|
+
const eventLedger = (options.initEventLedger ?? initEventLedger)();
|
|
224
|
+
const runStateStore = (options.initRunStateStore ?? initRunStateStore)();
|
|
225
|
+
try {
|
|
226
|
+
const rows = collectManageStatus({ portfolioQueue, eventLedger });
|
|
227
|
+
const runPortfolio = collectRunPortfolio({ portfolioQueue, eventLedger, runStateStore });
|
|
228
|
+
if (parsed.json) {
|
|
229
|
+
// Additive only (#4279): `rows` keeps its existing shape unchanged; `runPortfolio` is a new key so an
|
|
230
|
+
// existing consumer parsing this JSON for `rows` alone sees byte-identical output.
|
|
231
|
+
console.log(JSON.stringify({ rows, runPortfolio }, null, 2));
|
|
232
|
+
} else {
|
|
233
|
+
console.log(`${renderManageStatusTable(rows)}\n\n${renderRunPortfolioTable(runPortfolio)}`);
|
|
234
|
+
}
|
|
235
|
+
return 0;
|
|
236
|
+
} finally {
|
|
237
|
+
if (ownsPortfolioQueue) portfolioQueue.close();
|
|
238
|
+
if (ownsEventLedger) eventLedger.close();
|
|
239
|
+
if (ownsRunStateStore) runStateStore.close();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MinerPredictionMetricRow } from "@loopover/engine";
|
|
2
|
+
import type { PredictionLedger } from "./prediction-ledger.js";
|
|
3
|
+
|
|
4
|
+
export function collectPredictionMetricRows(ledger: PredictionLedger): MinerPredictionMetricRow[];
|
|
5
|
+
|
|
6
|
+
export function runMetrics(
|
|
7
|
+
args: string[],
|
|
8
|
+
options?: { initPredictionLedger?: () => PredictionLedger },
|
|
9
|
+
): number;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { renderMinerPredictionMetrics } from "@loopover/engine";
|
|
2
|
+
import { initPredictionLedger } from "./prediction-ledger.js";
|
|
3
|
+
import { argsWantJson, describeCliError, reportCliFailure } from "./cli-error.js";
|
|
4
|
+
|
|
5
|
+
// `metrics` (#4838): render the miner's prediction-calibration counters as Prometheus text-exposition to stdout,
|
|
6
|
+
// for a scrape wrapper or cron redirect. The counters are produced by the engine's already-built
|
|
7
|
+
// renderMinerPredictionMetrics (packages/gittensory-engine/src/miner-prediction-metrics.ts) -- this command only
|
|
8
|
+
// reads the local prediction ledger and feeds it in, never touching the renderer itself. Strictly local + offline:
|
|
9
|
+
// no network, no writes.
|
|
10
|
+
|
|
11
|
+
const METRICS_USAGE = "Usage: gittensory-miner metrics";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Project prediction-ledger rows onto the engine renderer's metric-row shape -- the predicted `conclusion` only.
|
|
15
|
+
* The realized-outcome pairing (`correct`) is intentionally left unset: the miner has no outcome-join yet, so the
|
|
16
|
+
* correct/incorrect counters stay zero and only `predictions_total{conclusion}` moves -- exactly how the renderer
|
|
17
|
+
* is designed to degrade before outcome-pairing exists (see its header comment).
|
|
18
|
+
*/
|
|
19
|
+
export function collectPredictionMetricRows(ledger) {
|
|
20
|
+
return ledger.readPredictions().map((entry) => ({ conclusion: entry.conclusion }));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Open the local prediction ledger (or a test-injected one) for the duration of `run`, closing it only when we
|
|
24
|
+
// opened it -- an injected ledger is owned by the caller. Mirrors event-ledger-cli.js's withEventLedger.
|
|
25
|
+
function withPredictionLedger(options, run) {
|
|
26
|
+
const ownsLedger = options.initPredictionLedger === undefined;
|
|
27
|
+
const ledger = (options.initPredictionLedger ?? initPredictionLedger)();
|
|
28
|
+
try {
|
|
29
|
+
return run(ledger);
|
|
30
|
+
} finally {
|
|
31
|
+
if (ownsLedger) ledger.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function runMetrics(args, options = {}) {
|
|
36
|
+
if (args.length > 0) {
|
|
37
|
+
return reportCliFailure(argsWantJson(args), METRICS_USAGE);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
return withPredictionLedger(options, (ledger) => {
|
|
42
|
+
// renderMinerPredictionMetrics returns a newline-terminated document; console.log re-adds the terminator, so
|
|
43
|
+
// trim it to emit exactly one trailing newline.
|
|
44
|
+
console.log(renderMinerPredictionMetrics(collectPredictionMetricRows(ledger)).trimEnd());
|
|
45
|
+
return 0;
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return reportCliFailure(argsWantJson(args), describeCliError(error));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type MigrateStatus = "skipped" | "up-to-date" | "migrated" | "failed";
|
|
2
|
+
|
|
3
|
+
export type MigrateResult = {
|
|
4
|
+
name: string;
|
|
5
|
+
dbPath: string;
|
|
6
|
+
ok: boolean;
|
|
7
|
+
status: MigrateStatus;
|
|
8
|
+
detail: string;
|
|
9
|
+
versionBefore: number | null;
|
|
10
|
+
versionAfter: number | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type MigrateStoreDescriptor = {
|
|
14
|
+
name: string;
|
|
15
|
+
resolveDbPath: (env?: Record<string, string | undefined>) => string;
|
|
16
|
+
open: (dbPath: string) => { close: () => void };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function runMigrateChecks(
|
|
20
|
+
env?: Record<string, string | undefined>,
|
|
21
|
+
stores?: MigrateStoreDescriptor[],
|
|
22
|
+
): MigrateResult[];
|
|
23
|
+
|
|
24
|
+
export function runMigrate(args?: string[], env?: Record<string, string | undefined>): number;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Proactive schema-migration runner for the miner's local SQLite stores (#4871). Every store already applies
|
|
2
|
+
// its own pending migrations (schema-version.js's applySchemaMigrations) as a side effect of being opened by
|
|
3
|
+
// whatever command happens to touch it first -- this command instead lets an operator PROACTIVELY bring every
|
|
4
|
+
// known store's EXISTING on-disk file up to date in one pass (e.g. right after upgrading, or before starting a
|
|
5
|
+
// fleet), without needing to guess which command happens to touch which store first. Mirrors status.js's
|
|
6
|
+
// storeIntegrityChecks [name, resolve*DbPath(env)] store list exactly (same seven stores `doctor` already
|
|
7
|
+
// covers), but actually OPENS each store (rather than a read-only integrity probe) so its real open/init
|
|
8
|
+
// function's migration path runs for real. A store file that does not exist yet is skipped, not created --
|
|
9
|
+
// "migrate" brings existing files up to date; it is not another way to bootstrap fresh state (that's `init`).
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { DatabaseSync } from "node:sqlite";
|
|
12
|
+
import { readSchemaVersion } from "./schema-version.js";
|
|
13
|
+
import { openClaimLedger, resolveClaimLedgerDbPath } from "./claim-ledger.js";
|
|
14
|
+
import { initEventLedger, resolveEventLedgerDbPath } from "./event-ledger.js";
|
|
15
|
+
import { initGovernorLedger, resolveGovernorLedgerDbPath } from "./governor-ledger.js";
|
|
16
|
+
import { initPredictionLedger, resolvePredictionLedgerDbPath } from "./prediction-ledger.js";
|
|
17
|
+
import { initPortfolioQueueStore, resolvePortfolioQueueDbPath } from "./portfolio-queue.js";
|
|
18
|
+
import { initRunStateStore, resolveRunStateDbPath } from "./run-state.js";
|
|
19
|
+
import { openPlanStore, resolvePlanStoreDbPath } from "./plan-store.js";
|
|
20
|
+
|
|
21
|
+
const STORES = [
|
|
22
|
+
{ name: "event-ledger", resolveDbPath: resolveEventLedgerDbPath, open: initEventLedger },
|
|
23
|
+
{ name: "governor-ledger", resolveDbPath: resolveGovernorLedgerDbPath, open: initGovernorLedger },
|
|
24
|
+
{ name: "prediction-ledger", resolveDbPath: resolvePredictionLedgerDbPath, open: initPredictionLedger },
|
|
25
|
+
{ name: "portfolio-queue", resolveDbPath: resolvePortfolioQueueDbPath, open: initPortfolioQueueStore },
|
|
26
|
+
{ name: "claim-ledger", resolveDbPath: resolveClaimLedgerDbPath, open: openClaimLedger },
|
|
27
|
+
{ name: "run-state", resolveDbPath: resolveRunStateDbPath, open: initRunStateStore },
|
|
28
|
+
{ name: "plan-store", resolveDbPath: resolvePlanStoreDbPath, open: openPlanStore },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/** Read a store file's stamped schema version without ever creating it -- matches checkStoreIntegrity's
|
|
32
|
+
* "not created yet" convention: an absent file has nothing to report a version for. */
|
|
33
|
+
function peekSchemaVersion(dbPath) {
|
|
34
|
+
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
35
|
+
try {
|
|
36
|
+
return readSchemaVersion(db);
|
|
37
|
+
} finally {
|
|
38
|
+
db.close();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Bring one store's EXISTING on-disk schema up to date. Never throws: a store that fails to open/migrate is
|
|
44
|
+
* reported as a failed result so one bad store cannot abort the whole sweep, matching doctor's per-store
|
|
45
|
+
* isolation. A store file that does not exist yet is reported as a clean skip (nothing to migrate), never
|
|
46
|
+
* created as a side effect of running this command.
|
|
47
|
+
* @returns {{ name: string, dbPath: string, ok: boolean, status: "skipped"|"up-to-date"|"migrated"|"failed", detail: string, versionBefore: number|null, versionAfter: number|null }}
|
|
48
|
+
*/
|
|
49
|
+
function migrateStore({ name, resolveDbPath, open }, env) {
|
|
50
|
+
const dbPath = resolveDbPath(env);
|
|
51
|
+
if (!existsSync(dbPath)) {
|
|
52
|
+
return {
|
|
53
|
+
name,
|
|
54
|
+
dbPath,
|
|
55
|
+
ok: true,
|
|
56
|
+
status: "skipped",
|
|
57
|
+
detail: "not created yet",
|
|
58
|
+
versionBefore: null,
|
|
59
|
+
versionAfter: null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// versionBefore is read INSIDE the same try as the migration itself: a corrupted file can throw on this very
|
|
63
|
+
// first read (a store that can't even be opened has no readable version either), and that must still surface
|
|
64
|
+
// as one failed store result rather than an uncaught exception aborting the whole sweep.
|
|
65
|
+
let versionBefore = null;
|
|
66
|
+
try {
|
|
67
|
+
versionBefore = peekSchemaVersion(dbPath);
|
|
68
|
+
const store = open(dbPath);
|
|
69
|
+
store.close();
|
|
70
|
+
const versionAfter = peekSchemaVersion(dbPath);
|
|
71
|
+
return {
|
|
72
|
+
name,
|
|
73
|
+
dbPath,
|
|
74
|
+
ok: true,
|
|
75
|
+
status: versionAfter > versionBefore ? "migrated" : "up-to-date",
|
|
76
|
+
detail: `v${versionBefore} -> v${versionAfter}`,
|
|
77
|
+
versionBefore,
|
|
78
|
+
versionAfter,
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return {
|
|
82
|
+
name,
|
|
83
|
+
dbPath,
|
|
84
|
+
ok: false,
|
|
85
|
+
status: "failed",
|
|
86
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
87
|
+
versionBefore,
|
|
88
|
+
versionAfter: versionBefore,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** `stores` is injectable so tests can exercise a store descriptor's failure paths (e.g. a non-Error throw)
|
|
94
|
+
* without depending on real node:sqlite error shapes; defaults to the real seven-store list. */
|
|
95
|
+
export function runMigrateChecks(env = process.env, stores = STORES) {
|
|
96
|
+
return stores.map((store) => migrateStore(store, env));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function runMigrate(args = [], env = process.env) {
|
|
100
|
+
const results = runMigrateChecks(env);
|
|
101
|
+
const failed = results.filter((result) => !result.ok);
|
|
102
|
+
if (args.includes("--json")) {
|
|
103
|
+
console.log(JSON.stringify({ ok: failed.length === 0, stores: results }, null, 2));
|
|
104
|
+
} else {
|
|
105
|
+
for (const result of results) {
|
|
106
|
+
console.log(`${result.ok ? result.status.padEnd(10) : "FAIL "} ${result.name}: ${result.detail}`);
|
|
107
|
+
}
|
|
108
|
+
if (failed.length > 0) console.error(`migrate: ${failed.length} store(s) failed`);
|
|
109
|
+
}
|
|
110
|
+
return failed.length === 0 ? 0 : 1;
|
|
111
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ParsedMinerGoalSpec } from "@loopover/engine";
|
|
2
|
+
|
|
3
|
+
export function resolveMinerGoalSpec(
|
|
4
|
+
repoPath: string,
|
|
5
|
+
options?: {
|
|
6
|
+
existsSync?: (path: string) => boolean;
|
|
7
|
+
openSync?: (path: string, flags: number) => number;
|
|
8
|
+
fstatSync?: (fd: number) => import("node:fs").Stats;
|
|
9
|
+
readSync?: (fd: number, buffer: Buffer, offset: number, length: number, position: number | null) => number;
|
|
10
|
+
closeSync?: (fd: number) => void;
|
|
11
|
+
},
|
|
12
|
+
): ParsedMinerGoalSpec;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { closeSync, constants, existsSync, fstatSync, openSync, readSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { discoverMinerGoalSpecPath, parseMinerGoalSpecContent } from "@loopover/engine";
|
|
4
|
+
|
|
5
|
+
const MAX_MINER_GOAL_SPEC_BYTES = 32_768;
|
|
6
|
+
|
|
7
|
+
// Real local .gittensory-miner.yml resolver (#5132, Wave 3.5 follow-up). MinerGoalSpec's own discovery
|
|
8
|
+
// helper (discoverMinerGoalSpecPath, packages/gittensory-engine) is deliberately IO-free -- the caller
|
|
9
|
+
// injects the existence check. Unlike self-review-context.js/rejection-signal.js/ams-policy.js, which fetch
|
|
10
|
+
// their target repo's files live over raw.githubusercontent.com BEFORE any clone exists, this resolver reads
|
|
11
|
+
// the ALREADY-CLONED repo on disk (attempt-worktree.js's prepareAttemptWorktree runs first in the real
|
|
12
|
+
// attempt-cli.js flow) -- no extra network round trip needed for a file that's already sitting in the
|
|
13
|
+
// worktree.
|
|
14
|
+
|
|
15
|
+
// Same convention as packages/gittensory-mcp/bin/gittensory-mcp.js's readCliTextFile: O_NOFOLLOW on open
|
|
16
|
+
// atomically rejects a symlinked path (no separate pre-open lstat -- that would be a check-then-open race, since
|
|
17
|
+
// a symlink can be swapped in between the lstat and the open). Bounds the READ itself, not just fstat's
|
|
18
|
+
// reported size, since a regular file can still grow between fstatSync and the read below.
|
|
19
|
+
function readRegularUtf8File(path, options) {
|
|
20
|
+
const openImpl = options.openSync ?? openSync;
|
|
21
|
+
const fstatImpl = options.fstatSync ?? fstatSync;
|
|
22
|
+
const readImpl = options.readSync ?? readSync;
|
|
23
|
+
const closeImpl = options.closeSync ?? closeSync;
|
|
24
|
+
|
|
25
|
+
const fd = openImpl(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
26
|
+
try {
|
|
27
|
+
const stats = fstatImpl(fd);
|
|
28
|
+
if (!stats.isFile() || stats.size > MAX_MINER_GOAL_SPEC_BYTES) return null;
|
|
29
|
+
const buffer = Buffer.alloc(MAX_MINER_GOAL_SPEC_BYTES + 1);
|
|
30
|
+
let bytesRead = 0;
|
|
31
|
+
while (bytesRead < buffer.length) {
|
|
32
|
+
const n = readImpl(fd, buffer, bytesRead, buffer.length - bytesRead, null);
|
|
33
|
+
if (n === 0) break;
|
|
34
|
+
bytesRead += n;
|
|
35
|
+
}
|
|
36
|
+
if (bytesRead > MAX_MINER_GOAL_SPEC_BYTES) return null;
|
|
37
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
38
|
+
} finally {
|
|
39
|
+
closeImpl(fd);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the real, parsed MinerGoalSpec for an already-cloned repo at `repoPath`, trying each
|
|
45
|
+
* MINER_GOAL_SPEC_FILENAMES candidate in the documented discovery order. Never throws: a missing file, an
|
|
46
|
+
* unreadable file, or malformed content all degrade to the tolerant parser's own absent/safe-default result.
|
|
47
|
+
*
|
|
48
|
+
* Injected filesystem operations receive the FULL joined path (same convention as `node:fs`'s own
|
|
49
|
+
* functions), not a repoPath-relative candidate.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} repoPath
|
|
52
|
+
* @param {{ existsSync?: (path: string) => boolean, openSync?: (path: string, flags: number) => number, fstatSync?: (fd: number) => import("node:fs").Stats, readSync?: (fd: number, buffer: Buffer, offset: number, length: number, position: number | null) => number, closeSync?: (fd: number) => void }} [options]
|
|
53
|
+
* @returns {import("@loopover/engine").ParsedMinerGoalSpec}
|
|
54
|
+
*/
|
|
55
|
+
export function resolveMinerGoalSpec(repoPath, options = {}) {
|
|
56
|
+
const existsImpl = options.existsSync ?? existsSync;
|
|
57
|
+
|
|
58
|
+
const relativePath = discoverMinerGoalSpecPath((candidate) => existsImpl(join(repoPath, candidate)));
|
|
59
|
+
if (!relativePath) return parseMinerGoalSpecContent(null);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const content = readRegularUtf8File(join(repoPath, relativePath), options);
|
|
63
|
+
return parseMinerGoalSpecContent(content);
|
|
64
|
+
} catch {
|
|
65
|
+
return parseMinerGoalSpecContent(null);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ForgeConfig } from "./forge-config.js";
|
|
2
|
+
import type { PolicyDocCache } from "./policy-doc-cache.js";
|
|
3
|
+
import type { PolicyVerdictCache } from "./policy-verdict-cache.js";
|
|
4
|
+
|
|
5
|
+
export type FanoutTarget = {
|
|
6
|
+
owner: string;
|
|
7
|
+
repo: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** Options shared by every fan-out entry point. `apiBaseUrl` is the legacy top-level forge-host override (it still
|
|
11
|
+
* wins over `forge.apiBaseUrl`); `forge` (#4784) carries the rest of the per-tenant forge knobs. `policyDocCache`,
|
|
12
|
+
* when supplied, lets discovery revalidate each repo's policy docs with a conditional GET instead of a full
|
|
13
|
+
* refetch (#4842). `policyVerdictCache`, when supplied, lets discovery reuse an already-resolved verdict once its
|
|
14
|
+
* deciding doc's ETag is confirmed unchanged, instead of re-resolving it (#4843). */
|
|
15
|
+
export type FanoutOptions = {
|
|
16
|
+
apiBaseUrl?: string;
|
|
17
|
+
forge?: Partial<ForgeConfig>;
|
|
18
|
+
concurrency?: number;
|
|
19
|
+
rateLimitLowWaterMark?: number;
|
|
20
|
+
rateLimitHighWaterMark?: number;
|
|
21
|
+
perPage?: number;
|
|
22
|
+
maxPages?: number;
|
|
23
|
+
sleepFn?: (ms: number) => Promise<unknown>;
|
|
24
|
+
policyDocCache?: PolicyDocCache | null;
|
|
25
|
+
policyVerdictCache?: PolicyVerdictCache | null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type RawCandidateIssue = {
|
|
29
|
+
owner: string;
|
|
30
|
+
repo: string;
|
|
31
|
+
repoFullName: string;
|
|
32
|
+
issueNumber: number;
|
|
33
|
+
title: string;
|
|
34
|
+
labels: string[];
|
|
35
|
+
commentsCount: number;
|
|
36
|
+
createdAt: string | null;
|
|
37
|
+
updatedAt: string | null;
|
|
38
|
+
htmlUrl: string | null;
|
|
39
|
+
aiPolicyAllowed: true;
|
|
40
|
+
aiPolicySource: "AI-USAGE.md" | "CONTRIBUTING.md" | "none";
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type CandidateIssueWarning = {
|
|
44
|
+
repoFullName: string;
|
|
45
|
+
stage: string;
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type CandidateIssueSummary = {
|
|
50
|
+
issues: RawCandidateIssue[];
|
|
51
|
+
rateLimitRemaining: number | null;
|
|
52
|
+
rateLimitResetAt: string | null;
|
|
53
|
+
warnings: CandidateIssueWarning[];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export function mapWithConcurrency<T, R>(
|
|
57
|
+
items: T[],
|
|
58
|
+
maxConcurrency: number,
|
|
59
|
+
worker: (item: T, index: number) => Promise<R>,
|
|
60
|
+
resolveLimit: () => number,
|
|
61
|
+
sleepFn?: (ms: number) => Promise<unknown>,
|
|
62
|
+
): Promise<R[]>;
|
|
63
|
+
|
|
64
|
+
export function fetchCandidateIssuesWithSummary(
|
|
65
|
+
targets: FanoutTarget[],
|
|
66
|
+
githubToken: string,
|
|
67
|
+
options?: FanoutOptions,
|
|
68
|
+
): Promise<CandidateIssueSummary>;
|
|
69
|
+
|
|
70
|
+
export function fetchCandidateIssues(
|
|
71
|
+
targets: FanoutTarget[],
|
|
72
|
+
githubToken: string,
|
|
73
|
+
options?: FanoutOptions,
|
|
74
|
+
): Promise<RawCandidateIssue[]>;
|
|
75
|
+
|
|
76
|
+
export function searchCandidateIssuesWithSummary(
|
|
77
|
+
searchQuery: string,
|
|
78
|
+
githubToken: string,
|
|
79
|
+
options?: FanoutOptions,
|
|
80
|
+
): Promise<CandidateIssueSummary>;
|
|
81
|
+
|
|
82
|
+
export function searchCandidateIssues(
|
|
83
|
+
searchQuery: string,
|
|
84
|
+
githubToken: string,
|
|
85
|
+
options?: FanoutOptions,
|
|
86
|
+
): Promise<RawCandidateIssue[]>;
|