@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,99 @@
|
|
|
1
|
+
// Calibration report: join the miner's own predicted gate verdicts with the realized outcomes it later observed
|
|
2
|
+
// (#4849). Read-only aggregation only — it never touches the live scoring/calibration logic that feeds the gate
|
|
3
|
+
// (maintainer-owned). Builds on the types-only scaffolding in calibration-types.js.
|
|
4
|
+
import {
|
|
5
|
+
isCalibrationReport,
|
|
6
|
+
isCalibrationRow,
|
|
7
|
+
isObservedOutcomeRecord,
|
|
8
|
+
isPredictedVerdictRecord,
|
|
9
|
+
} from "./calibration-types.js";
|
|
10
|
+
|
|
11
|
+
export { isCalibrationReport, isCalibrationRow, isObservedOutcomeRecord, isPredictedVerdictRecord };
|
|
12
|
+
|
|
13
|
+
/** Normalize a decision string to the calibration vocabulary (`merge` / `close` / `hold`), or `""` when it is
|
|
14
|
+
* unrecognized. `value` is always the already-validated non-empty string field of a record (the type guards run
|
|
15
|
+
* first), so no non-string handling is needed here. Accepts both the predicted (`merge`/`close`/`hold`) and the
|
|
16
|
+
* realized (`merged`/`closed`) forms. */
|
|
17
|
+
function normalizeDecision(value) {
|
|
18
|
+
const decision = value.trim().toLowerCase();
|
|
19
|
+
if (decision === "merge" || decision === "merged") return "merge";
|
|
20
|
+
if (decision === "close" || decision === "closed") return "close";
|
|
21
|
+
if (decision === "hold") return "hold";
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function emptyRow(project) {
|
|
26
|
+
return {
|
|
27
|
+
project,
|
|
28
|
+
wouldMerge: 0,
|
|
29
|
+
mergeConfirmed: 0,
|
|
30
|
+
mergeFalse: 0,
|
|
31
|
+
wouldClose: 0,
|
|
32
|
+
closeConfirmed: 0,
|
|
33
|
+
closeFalse: 0,
|
|
34
|
+
hold: 0,
|
|
35
|
+
decided: 0,
|
|
36
|
+
mergePrecision: null,
|
|
37
|
+
closePrecision: null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Key a record by its (project, targetId). Project and targetId are validated non-empty strings; the space
|
|
42
|
+
// separator is fine for keying (collisions across different (project, targetId) pairs are astronomically
|
|
43
|
+
// unlikely and would only merge two projects' tallies, never fabricate a false one).
|
|
44
|
+
function recordKey(project, targetId) {
|
|
45
|
+
return `${project} ${targetId}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Join predicted-verdict records with realized-outcome records into a per-project calibration report. Pure and
|
|
50
|
+
* read-only. A prediction counts as "decided" only when a realized outcome for the SAME `(project, targetId)`
|
|
51
|
+
* exists AND resolves to a clear `merge` or `close`; a still-pending prediction (no outcome) or one whose outcome
|
|
52
|
+
* is unrecognized is skipped. Per project it tallies the confusion matrix (would-merge/close vs confirmed/false,
|
|
53
|
+
* plus holds) and derives merge/close precision (null below one relevant sample). Malformed records on either
|
|
54
|
+
* side are ignored. Rows are sorted by project for a stable render.
|
|
55
|
+
*
|
|
56
|
+
* @param {import("./calibration-types.js").PredictedVerdictRecord[]} predictions
|
|
57
|
+
* @param {import("./calibration-types.js").ObservedOutcomeRecord[]} outcomes
|
|
58
|
+
* @returns {import("./calibration-types.js").CalibrationReport}
|
|
59
|
+
*/
|
|
60
|
+
export function buildCalibrationReport(predictions, outcomes) {
|
|
61
|
+
const outcomeByKey = new Map();
|
|
62
|
+
for (const outcome of Array.isArray(outcomes) ? outcomes : []) {
|
|
63
|
+
if (!isObservedOutcomeRecord(outcome)) continue;
|
|
64
|
+
outcomeByKey.set(recordKey(outcome.project, outcome.targetId), normalizeDecision(outcome.outcomeDecision));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const byProject = new Map();
|
|
68
|
+
for (const prediction of Array.isArray(predictions) ? predictions : []) {
|
|
69
|
+
if (!isPredictedVerdictRecord(prediction)) continue;
|
|
70
|
+
const observed = outcomeByKey.get(recordKey(prediction.project, prediction.targetId));
|
|
71
|
+
if (observed !== "merge" && observed !== "close") continue; // pending or unclassifiable outcome
|
|
72
|
+
let row = byProject.get(prediction.project);
|
|
73
|
+
if (!row) {
|
|
74
|
+
row = emptyRow(prediction.project);
|
|
75
|
+
byProject.set(prediction.project, row);
|
|
76
|
+
}
|
|
77
|
+
row.decided += 1;
|
|
78
|
+
const predicted = normalizeDecision(prediction.predictedDecision);
|
|
79
|
+
if (predicted === "merge") {
|
|
80
|
+
row.wouldMerge += 1;
|
|
81
|
+
if (observed === "merge") row.mergeConfirmed += 1;
|
|
82
|
+
else row.mergeFalse += 1;
|
|
83
|
+
} else if (predicted === "close") {
|
|
84
|
+
row.wouldClose += 1;
|
|
85
|
+
if (observed === "close") row.closeConfirmed += 1;
|
|
86
|
+
else row.closeFalse += 1;
|
|
87
|
+
} else if (predicted === "hold") {
|
|
88
|
+
row.hold += 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const rows = [...byProject.values()].sort((a, b) => a.project.localeCompare(b.project));
|
|
93
|
+
for (const row of rows) {
|
|
94
|
+
row.mergePrecision = row.wouldMerge > 0 ? row.mergeConfirmed / row.wouldMerge : null;
|
|
95
|
+
row.closePrecision = row.wouldClose > 0 ? row.closeConfirmed / row.wouldClose : null;
|
|
96
|
+
}
|
|
97
|
+
// Signal exists once any project carries at least one decided (predicted-then-realized) sample.
|
|
98
|
+
return { hasSignal: rows.length > 0, rows };
|
|
99
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type CheckRunConclusion = "pending" | "success" | "failure" | "neutral";
|
|
2
|
+
|
|
3
|
+
export type NormalizedCheckRun = {
|
|
4
|
+
name: string;
|
|
5
|
+
status: string;
|
|
6
|
+
conclusion: CheckRunConclusion;
|
|
7
|
+
detailsUrl: string | null;
|
|
8
|
+
startedAt: string | null;
|
|
9
|
+
completedAt: string | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type PollCheckRunsResult = {
|
|
13
|
+
conclusion: CheckRunConclusion;
|
|
14
|
+
checks: NormalizedCheckRun[];
|
|
15
|
+
headSha: string;
|
|
16
|
+
attempts: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type PollCheckRunsOptions = {
|
|
20
|
+
apiBaseUrl?: string;
|
|
21
|
+
fetchFn?: typeof fetch;
|
|
22
|
+
githubToken?: string;
|
|
23
|
+
maxAttempts?: number;
|
|
24
|
+
minIntervalMs?: number;
|
|
25
|
+
maxIntervalMs?: number;
|
|
26
|
+
sleepFn?: (delayMs: number) => Promise<unknown>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function pollCheckRuns(
|
|
30
|
+
repoFullName: string,
|
|
31
|
+
prNumber: number,
|
|
32
|
+
options?: PollCheckRunsOptions,
|
|
33
|
+
): Promise<PollCheckRunsResult>;
|
package/lib/ci-poller.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { fetchWithRetry } from "./http-retry.js";
|
|
2
|
+
|
|
3
|
+
const defaultApiBaseUrl = "https://api.github.com";
|
|
4
|
+
const defaultMinIntervalMs = 60_000;
|
|
5
|
+
const defaultMaxIntervalMs = 5 * 60_000;
|
|
6
|
+
const defaultMaxAttempts = 1;
|
|
7
|
+
const githubApiVersion = "2022-11-28";
|
|
8
|
+
|
|
9
|
+
function normalizeApiBaseUrl(value) {
|
|
10
|
+
if (value === undefined) return defaultApiBaseUrl;
|
|
11
|
+
if (typeof value !== "string" || !value.trim()) return defaultApiBaseUrl;
|
|
12
|
+
let parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = new URL(value.trim());
|
|
15
|
+
} catch {
|
|
16
|
+
throw new Error("invalid_api_base_url");
|
|
17
|
+
}
|
|
18
|
+
if (parsed.protocol !== "https:" || parsed.hostname !== "api.github.com") {
|
|
19
|
+
throw new Error("invalid_api_base_url");
|
|
20
|
+
}
|
|
21
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/, "");
|
|
22
|
+
parsed.search = "";
|
|
23
|
+
parsed.hash = "";
|
|
24
|
+
return parsed.toString().replace(/\/+$/, "");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizePositiveInt(value, fallback, min, max) {
|
|
28
|
+
if (!Number.isFinite(value)) return fallback;
|
|
29
|
+
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeOptions(options = {}) {
|
|
33
|
+
return {
|
|
34
|
+
apiBaseUrl: normalizeApiBaseUrl(options.apiBaseUrl),
|
|
35
|
+
fetchFn: options.fetchFn ?? fetch,
|
|
36
|
+
githubToken: typeof options.githubToken === "string" ? options.githubToken.trim() : "",
|
|
37
|
+
maxAttempts: normalizePositiveInt(options.maxAttempts, defaultMaxAttempts, 1, 20),
|
|
38
|
+
minIntervalMs: normalizePositiveInt(options.minIntervalMs, defaultMinIntervalMs, 1, 60 * 60_000),
|
|
39
|
+
maxIntervalMs: normalizePositiveInt(options.maxIntervalMs, defaultMaxIntervalMs, 1, 60 * 60_000),
|
|
40
|
+
sleepFn:
|
|
41
|
+
options.sleepFn ??
|
|
42
|
+
((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs))),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseRepoFullName(repoFullName) {
|
|
47
|
+
if (typeof repoFullName !== "string") throw new Error("invalid_repo_full_name");
|
|
48
|
+
const [owner, repo, extra] = repoFullName.split("/");
|
|
49
|
+
if (!owner?.trim() || !repo?.trim() || extra !== undefined) {
|
|
50
|
+
throw new Error("invalid_repo_full_name");
|
|
51
|
+
}
|
|
52
|
+
return { owner: owner.trim(), repo: repo.trim() };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizePullNumber(value) {
|
|
56
|
+
if (!Number.isInteger(value) || value <= 0) throw new Error("invalid_pr_number");
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function githubHeaders(githubToken) {
|
|
61
|
+
const headers = {
|
|
62
|
+
accept: "application/vnd.github+json",
|
|
63
|
+
"user-agent": "loopover-miner",
|
|
64
|
+
"x-github-api-version": githubApiVersion,
|
|
65
|
+
};
|
|
66
|
+
if (githubToken) headers.authorization = `Bearer ${githubToken}`;
|
|
67
|
+
return headers;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function repoPath(target, suffix) {
|
|
71
|
+
return `/repos/${encodeURIComponent(target.owner)}/${encodeURIComponent(target.repo)}${suffix}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function apiUrl(apiBaseUrl, path, query = "") {
|
|
75
|
+
return `${apiBaseUrl}${path}${query}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function githubError(response, payload) {
|
|
79
|
+
const code = `github_${response.status}`;
|
|
80
|
+
const githubMessage =
|
|
81
|
+
typeof payload?.message === "string" && payload.message.trim() ? payload.message : null;
|
|
82
|
+
const message = githubMessage ? `${code}: ${githubMessage}` : code;
|
|
83
|
+
return Object.assign(new Error(message), { code, githubMessage });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function githubGetJsonResponse(url, options) {
|
|
87
|
+
// Retry transient network errors / 5xx around this single call (#4829), distinct from the poller's own
|
|
88
|
+
// pending-retry loop; the poller's injected sleepFn keeps tests instant.
|
|
89
|
+
const response = await fetchWithRetry(
|
|
90
|
+
options.fetchFn,
|
|
91
|
+
url,
|
|
92
|
+
{ method: "GET", headers: githubHeaders(options.githubToken) },
|
|
93
|
+
{ sleepFn: options.sleepFn },
|
|
94
|
+
);
|
|
95
|
+
const payload = await response.json().catch(() => null);
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw githubError(response, payload);
|
|
98
|
+
}
|
|
99
|
+
return { payload, response };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function githubGetJson(url, options) {
|
|
103
|
+
const { payload } = await githubGetJsonResponse(url, options);
|
|
104
|
+
return payload;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function hasNextLink(response) {
|
|
108
|
+
return /<[^>]+>;\s*rel="next"/.test(response.headers.get("link") ?? "");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function payloadTotalCount(payload) {
|
|
112
|
+
const totalCount = Number(payload?.total_count);
|
|
113
|
+
return Number.isInteger(totalCount) && totalCount >= 0 ? totalCount : null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeConclusion(checkRun) {
|
|
117
|
+
if (!checkRun || typeof checkRun !== "object") return "pending";
|
|
118
|
+
if (checkRun.status !== "completed") return "pending";
|
|
119
|
+
switch (checkRun.conclusion) {
|
|
120
|
+
case "success":
|
|
121
|
+
case "skipped":
|
|
122
|
+
return "success";
|
|
123
|
+
case "neutral":
|
|
124
|
+
return "neutral";
|
|
125
|
+
case "failure":
|
|
126
|
+
case "cancelled":
|
|
127
|
+
case "timed_out":
|
|
128
|
+
case "action_required":
|
|
129
|
+
case "stale":
|
|
130
|
+
case "startup_failure":
|
|
131
|
+
return "failure";
|
|
132
|
+
default:
|
|
133
|
+
return "pending";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function normalizeCheckRun(checkRun) {
|
|
138
|
+
return {
|
|
139
|
+
name: typeof checkRun?.name === "string" ? checkRun.name : "",
|
|
140
|
+
status: typeof checkRun?.status === "string" ? checkRun.status : "unknown",
|
|
141
|
+
conclusion: normalizeConclusion(checkRun),
|
|
142
|
+
detailsUrl: typeof checkRun?.details_url === "string" ? checkRun.details_url : null,
|
|
143
|
+
startedAt: typeof checkRun?.started_at === "string" ? checkRun.started_at : null,
|
|
144
|
+
completedAt: typeof checkRun?.completed_at === "string" ? checkRun.completed_at : null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function aggregateConclusion(checks) {
|
|
149
|
+
if (checks.length === 0) return "pending";
|
|
150
|
+
if (checks.some((check) => check.conclusion === "failure")) return "failure";
|
|
151
|
+
if (checks.some((check) => check.conclusion === "pending")) return "pending";
|
|
152
|
+
if (checks.every((check) => check.conclusion === "success")) return "success";
|
|
153
|
+
return "neutral";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function backoffDelayMs(attemptIndex, options) {
|
|
157
|
+
const exponent = Math.min(10, Math.max(0, attemptIndex));
|
|
158
|
+
return Math.min(options.maxIntervalMs, options.minIntervalMs * 2 ** exponent);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function fetchHeadSha(target, prNumber, options) {
|
|
162
|
+
const payload = await githubGetJson(
|
|
163
|
+
apiUrl(options.apiBaseUrl, repoPath(target, `/pulls/${prNumber}`)),
|
|
164
|
+
options,
|
|
165
|
+
);
|
|
166
|
+
const headSha = payload?.head?.sha;
|
|
167
|
+
if (typeof headSha !== "string" || !headSha) throw new Error("github_pr_head_sha_missing");
|
|
168
|
+
return headSha;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function fetchCheckRuns(target, headSha, options) {
|
|
172
|
+
const checks = [];
|
|
173
|
+
let page = 1;
|
|
174
|
+
let expectedTotalCount = null;
|
|
175
|
+
while (true) {
|
|
176
|
+
const { payload, response } = await githubGetJsonResponse(
|
|
177
|
+
apiUrl(
|
|
178
|
+
options.apiBaseUrl,
|
|
179
|
+
repoPath(target, `/commits/${encodeURIComponent(headSha)}/check-runs`),
|
|
180
|
+
`?per_page=100&page=${page}`,
|
|
181
|
+
),
|
|
182
|
+
options,
|
|
183
|
+
);
|
|
184
|
+
if (!Array.isArray(payload?.check_runs)) {
|
|
185
|
+
throw new Error("github_check_runs_malformed");
|
|
186
|
+
}
|
|
187
|
+
const pageChecks = payload.check_runs.map(normalizeCheckRun);
|
|
188
|
+
checks.push(...pageChecks);
|
|
189
|
+
expectedTotalCount = payloadTotalCount(payload) ?? expectedTotalCount;
|
|
190
|
+
if (!hasNextLink(response) && (expectedTotalCount === null || checks.length >= expectedTotalCount)) {
|
|
191
|
+
return checks;
|
|
192
|
+
}
|
|
193
|
+
if (pageChecks.length === 0) {
|
|
194
|
+
throw new Error("github_check_runs_pagination_incomplete");
|
|
195
|
+
}
|
|
196
|
+
page += 1;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function pollCheckRuns(repoFullName, prNumber, options = {}) {
|
|
201
|
+
const target = parseRepoFullName(repoFullName);
|
|
202
|
+
const normalizedPrNumber = normalizePullNumber(prNumber);
|
|
203
|
+
const normalizedOptions = normalizeOptions(options);
|
|
204
|
+
|
|
205
|
+
let latest = { conclusion: "pending", checks: [], headSha: "", attempts: 0 };
|
|
206
|
+
for (let attempt = 0; attempt < normalizedOptions.maxAttempts; attempt += 1) {
|
|
207
|
+
const headSha = await fetchHeadSha(target, normalizedPrNumber, normalizedOptions);
|
|
208
|
+
const checks = await fetchCheckRuns(target, headSha, normalizedOptions);
|
|
209
|
+
latest = {
|
|
210
|
+
conclusion: aggregateConclusion(checks),
|
|
211
|
+
checks,
|
|
212
|
+
headSha,
|
|
213
|
+
attempts: attempt + 1,
|
|
214
|
+
};
|
|
215
|
+
if (latest.conclusion !== "pending") {
|
|
216
|
+
const currentHeadSha = await fetchHeadSha(target, normalizedPrNumber, normalizedOptions);
|
|
217
|
+
if (currentHeadSha === headSha) {
|
|
218
|
+
return latest;
|
|
219
|
+
}
|
|
220
|
+
latest = {
|
|
221
|
+
conclusion: "pending",
|
|
222
|
+
checks: [],
|
|
223
|
+
headSha: currentHeadSha,
|
|
224
|
+
attempts: attempt + 1,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (attempt === normalizedOptions.maxAttempts - 1) {
|
|
228
|
+
return latest;
|
|
229
|
+
}
|
|
230
|
+
await normalizedOptions.sleepFn(backoffDelayMs(attempt, normalizedOptions));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return latest;
|
|
234
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** An observed claim on an issue: a PR/claimant number plus when it claimed the linked issue (if known). */
|
|
2
|
+
export type ObservedClaim = {
|
|
3
|
+
number: number;
|
|
4
|
+
claimedAt?: string | null | undefined;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/** The engine `DuplicateClaimMember` shape this module bridges an {@link ObservedClaim} to. */
|
|
8
|
+
export type ClaimMember = {
|
|
9
|
+
number: number;
|
|
10
|
+
linkedIssueClaimedAt: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** The adjudication result: the go/no-go `isWinner`, plus a DISPLAY-only `winnerNumber` (null when not determinable). */
|
|
14
|
+
export type ClaimAdjudication = {
|
|
15
|
+
isWinner: boolean;
|
|
16
|
+
winnerNumber: number | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function toClaimMember(claim: ObservedClaim): ClaimMember;
|
|
20
|
+
|
|
21
|
+
export function adjudicateSoftClaim(self: ObservedClaim, competing?: readonly ObservedClaim[]): ClaimAdjudication;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Soft-claim adjudication (#4291). Decides which of several miners claiming the same issue proceeds, by REUSING the
|
|
2
|
+
// maintainer-side duplicate-cluster election (`isDuplicateClusterWinnerByClaim` from @loopover/engine)
|
|
3
|
+
// rather than reimplementing it — so the miner and the maintainer gate agree on exactly one winner by construction.
|
|
4
|
+
//
|
|
5
|
+
// The local claim ledger is 100% client-side and cannot see other miners' claims, so the competing-claim signal
|
|
6
|
+
// must come from something publicly observable: the OPEN PRs that link the same issue (an issue with several open
|
|
7
|
+
// PRs linking it IS the public signal of a contested claim). The caller assembles that set — exactly like the
|
|
8
|
+
// maintainer-side callers in src/ do — and passes it here.
|
|
9
|
+
import { isDuplicateClusterWinnerByClaim, resolveDuplicateClusterWinnerNumber } from "@loopover/engine";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Map an observed claim record to the engine's `DuplicateClaimMember`. The field names deliberately DIFFER — the
|
|
13
|
+
* local ledger / observed data expose `claimedAt`, the engine election reads `linkedIssueClaimedAt` — so the bridge
|
|
14
|
+
* is explicit (they are not interchangeable by accident of naming). `createdAt` is intentionally omitted: the
|
|
15
|
+
* election ignores it (an older PR can claim a linked issue later by editing its body). Pure.
|
|
16
|
+
*/
|
|
17
|
+
export function toClaimMember(claim) {
|
|
18
|
+
return { number: claim.number, linkedIssueClaimedAt: claim.claimedAt ?? null };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Adjudicate whether THIS miner's soft-claim wins a contested issue. `self` is this miner's claim and `competing`
|
|
23
|
+
* is the publicly-observable set of OTHER open PRs linking the same issue; each entry is `{ number, claimedAt }`.
|
|
24
|
+
* Returns the go/no-go `isWinner` (driven ONLY by `isDuplicateClusterWinnerByClaim`) plus a DISPLAY-only
|
|
25
|
+
* `winnerNumber` (from `resolveDuplicateClusterWinnerNumber`, for surfacing "you lost this claim to PR #N" to the
|
|
26
|
+
* operator — never for the decision). Pure — no IO. Fail-closed: a missing/sparse claim time loses; the winner is
|
|
27
|
+
* `null` when the ordering is too sparse to be sure (it never guesses). An empty `competing` list ⇒ trivial winner.
|
|
28
|
+
*/
|
|
29
|
+
export function adjudicateSoftClaim(self, competing = []) {
|
|
30
|
+
const selfMember = toClaimMember(self);
|
|
31
|
+
const siblings = competing.map(toClaimMember);
|
|
32
|
+
return {
|
|
33
|
+
isWinner: isDuplicateClusterWinnerByClaim(selfMember, siblings),
|
|
34
|
+
winnerNumber: resolveDuplicateClusterWinnerNumber(selfMember, siblings),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { LiveIssueSnapshot } from "./submission-freshness-check.js";
|
|
2
|
+
import type { ObservedClaim } from "./claim-adjudication.js";
|
|
3
|
+
import type { LocalWriteActionSpec } from "@loopover/engine";
|
|
4
|
+
|
|
5
|
+
export function assembleCompetingClaims(
|
|
6
|
+
snapshot: LiveIssueSnapshot | null | undefined,
|
|
7
|
+
selfPrNumber: number,
|
|
8
|
+
minerLogin: string,
|
|
9
|
+
): ObservedClaim[];
|
|
10
|
+
|
|
11
|
+
export type ClaimConflictInput = {
|
|
12
|
+
repoFullName: string;
|
|
13
|
+
issueNumber: number;
|
|
14
|
+
selfPrNumber: number;
|
|
15
|
+
selfClaimedAt: string | null;
|
|
16
|
+
minerLogin: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ClaimConflictDeps = {
|
|
20
|
+
fetchLiveIssueSnapshot: (repoFullName: string, issueNumber: number) => Promise<LiveIssueSnapshot | null>;
|
|
21
|
+
executeLocalWrite: (spec: LocalWriteActionSpec) => Promise<unknown>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ClaimConflictResult =
|
|
25
|
+
| { checked: false; reason: "live_state_unavailable" }
|
|
26
|
+
| { checked: true; isWinner: true; winnerNumber: number | null; competingCount: number }
|
|
27
|
+
| { checked: true; isWinner: false; winnerNumber: number | null; competingCount: number; closeResult: unknown };
|
|
28
|
+
|
|
29
|
+
export function resolveClaimConflict(input: ClaimConflictInput, deps: ClaimConflictDeps): Promise<ClaimConflictResult>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Real claim-conflict resolution (#4848): the missing piece over claim-adjudication.js's own adjudicator,
|
|
2
|
+
// which is correct and well-tested in isolation but has no caller that assembles a REAL competing-claims set.
|
|
3
|
+
// checkSubmissionFreshness (submission-freshness-check.js) already catches the common case pre-submission --
|
|
4
|
+
// aborting before open_pr if another author's PR already references the issue -- but that check can only see
|
|
5
|
+
// what's PUBLIC at the moment it runs. Two miners racing closely enough that BOTH pass their own freshness
|
|
6
|
+
// check before either's PR exists yet is a genuine TOCTOU window freshness cannot close. This module is the
|
|
7
|
+
// POST-submission reconciliation for exactly that window: once THIS miner's PR is real and public, check
|
|
8
|
+
// whether ANOTHER open PR also claims the same issue and, if this miner's claim loses the election, close its
|
|
9
|
+
// own just-opened PR (never anyone else's) -- the write action the contributor-vs-maintainer safety framework
|
|
10
|
+
// keeps maintainer-only (#4833's own scope note), since it means the autonomous loop acts on a race-resolution
|
|
11
|
+
// decision with no human review.
|
|
12
|
+
//
|
|
13
|
+
// CLAIM-TIME ASYMMETRY (documented, not accidental): `self`'s claimedAt is the miner's OWN real local
|
|
14
|
+
// claim-ledger timestamp (claim-ledger.js, recorded before work even started). A competing PR's claimedAt uses
|
|
15
|
+
// its real GitHub `createdAt` instead -- the maintainer gate's own duplicate-winner election uses gittensory
|
|
16
|
+
// server's "first observed this PR's linked-issue set" timestamp, but that requires a continuous, persistent
|
|
17
|
+
// observation history this stateless client-side tool does not have for a PR it doesn't own. `createdAt` is
|
|
18
|
+
// the best real, publicly-observable proxy available for someone else's PR -- live-issue-snapshot.js's own
|
|
19
|
+
// comment on `createdAt` explains this in more detail.
|
|
20
|
+
//
|
|
21
|
+
// EVENTUAL CONSISTENCY: this checks GitHub's live state immediately after submission. A competing PR that
|
|
22
|
+
// exists but hasn't yet propagated through GitHub's own search/GraphQL indexing in that instant would be
|
|
23
|
+
// invisible to this one-shot check -- there is no retry/backoff here, which would be its own separate scope.
|
|
24
|
+
|
|
25
|
+
import { adjudicateSoftClaim } from "./claim-adjudication.js";
|
|
26
|
+
import { buildClosePrSpec } from "@loopover/engine";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Assemble the real competing-claims set from a fetched LiveIssueSnapshot: every OTHER open PR referencing
|
|
30
|
+
* the issue, excluding `selfPrNumber` and any PR authored by `minerLogin` itself (case-insensitive, mirrors
|
|
31
|
+
* checkSubmissionFreshness's own author comparison -- a login can be echoed back with different casing).
|
|
32
|
+
* Excluding same-author PRs is deliberate, not an edge case slipping through: a miner never competes against
|
|
33
|
+
* its own work, so if this login somehow has ANOTHER open PR on the same issue (e.g. a retry after a crash
|
|
34
|
+
* left a stale one behind), that PR is never treated as a competing claim to lose against -- only a genuinely
|
|
35
|
+
* different claimant's PR can trigger a real close.
|
|
36
|
+
* Pure given its inputs.
|
|
37
|
+
*
|
|
38
|
+
* @param {import("./submission-freshness-check.js").LiveIssueSnapshot | null | undefined} snapshot
|
|
39
|
+
* @param {number} selfPrNumber
|
|
40
|
+
* @param {string} minerLogin
|
|
41
|
+
* @returns {import("./claim-adjudication.js").ObservedClaim[]}
|
|
42
|
+
*/
|
|
43
|
+
export function assembleCompetingClaims(snapshot, selfPrNumber, minerLogin) {
|
|
44
|
+
const minerLoginKey = minerLogin.trim().toLowerCase();
|
|
45
|
+
const referencingPrs = Array.isArray(snapshot?.referencingPrs) ? snapshot.referencingPrs : [];
|
|
46
|
+
return referencingPrs
|
|
47
|
+
.filter((pr) => pr.state === "open" && pr.number !== selfPrNumber)
|
|
48
|
+
.filter((pr) => typeof pr.authorLogin !== "string" || pr.authorLogin.trim().toLowerCase() !== minerLoginKey)
|
|
49
|
+
.map((pr) => ({ number: pr.number, claimedAt: pr.createdAt ?? null }));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a real claim conflict for an already-submitted PR. Fails OPEN (never closes anything) when the live
|
|
54
|
+
* snapshot can't be fetched -- an unavailable check is not evidence of a lost claim.
|
|
55
|
+
*
|
|
56
|
+
* @param {{ repoFullName: string, issueNumber: number, selfPrNumber: number, selfClaimedAt: string | null, minerLogin: string }} input
|
|
57
|
+
* @param {{
|
|
58
|
+
* fetchLiveIssueSnapshot: (repoFullName: string, issueNumber: number) => Promise<import("./submission-freshness-check.js").LiveIssueSnapshot | null>,
|
|
59
|
+
* executeLocalWrite: (spec: import("@loopover/engine").LocalWriteActionSpec) => Promise<unknown>,
|
|
60
|
+
* }} deps
|
|
61
|
+
* @returns {Promise<{
|
|
62
|
+
* checked: boolean,
|
|
63
|
+
* reason?: "live_state_unavailable",
|
|
64
|
+
* isWinner?: boolean,
|
|
65
|
+
* winnerNumber?: number | null,
|
|
66
|
+
* competingCount?: number,
|
|
67
|
+
* closeResult?: unknown,
|
|
68
|
+
* }>}
|
|
69
|
+
*/
|
|
70
|
+
export async function resolveClaimConflict(input, deps) {
|
|
71
|
+
let snapshot;
|
|
72
|
+
try {
|
|
73
|
+
snapshot = await deps.fetchLiveIssueSnapshot(input.repoFullName, input.issueNumber);
|
|
74
|
+
} catch {
|
|
75
|
+
snapshot = null;
|
|
76
|
+
}
|
|
77
|
+
if (!snapshot || typeof snapshot !== "object") {
|
|
78
|
+
return { checked: false, reason: "live_state_unavailable" };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const competing = assembleCompetingClaims(snapshot, input.selfPrNumber, input.minerLogin);
|
|
82
|
+
const adjudication = adjudicateSoftClaim({ number: input.selfPrNumber, claimedAt: input.selfClaimedAt }, competing);
|
|
83
|
+
|
|
84
|
+
if (adjudication.isWinner) {
|
|
85
|
+
return { checked: true, isWinner: true, winnerNumber: adjudication.winnerNumber, competingCount: competing.length };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const comment = adjudication.winnerNumber
|
|
89
|
+
? `Closing this PR: pull request #${adjudication.winnerNumber} claimed this issue first. This is an automated soft-claim conflict resolution -- no action needed from you.`
|
|
90
|
+
: `Closing this PR: another open pull request already claims this issue. This is an automated soft-claim conflict resolution -- no action needed from you.`;
|
|
91
|
+
const spec = buildClosePrSpec({ repoFullName: input.repoFullName, number: input.selfPrNumber, comment });
|
|
92
|
+
const closeResult = await deps.executeLocalWrite(spec);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
checked: true,
|
|
96
|
+
isWinner: false,
|
|
97
|
+
winnerNumber: adjudication.winnerNumber,
|
|
98
|
+
competingCount: competing.length,
|
|
99
|
+
closeResult,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ClaimEntry, ClaimLedger, ClaimStatus } from "./claim-ledger.js";
|
|
2
|
+
|
|
3
|
+
export type ParsedClaimClaimArgs =
|
|
4
|
+
| {
|
|
5
|
+
repoFullName: string;
|
|
6
|
+
issueNumber: number;
|
|
7
|
+
note: string | undefined;
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
json: boolean;
|
|
10
|
+
apiBaseUrl: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
| { error: string };
|
|
13
|
+
|
|
14
|
+
export type ParsedClaimReleaseArgs =
|
|
15
|
+
| {
|
|
16
|
+
repoFullName: string;
|
|
17
|
+
issueNumber: number;
|
|
18
|
+
dryRun: boolean;
|
|
19
|
+
json: boolean;
|
|
20
|
+
apiBaseUrl: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
| { error: string };
|
|
23
|
+
|
|
24
|
+
export type ParsedClaimListArgs =
|
|
25
|
+
| {
|
|
26
|
+
json: boolean;
|
|
27
|
+
repoFullName: string | null;
|
|
28
|
+
status: ClaimStatus | null;
|
|
29
|
+
}
|
|
30
|
+
| { error: string };
|
|
31
|
+
|
|
32
|
+
export function parseClaimClaimArgs(args: string[]): ParsedClaimClaimArgs;
|
|
33
|
+
|
|
34
|
+
export function parseClaimReleaseArgs(args: string[]): ParsedClaimReleaseArgs;
|
|
35
|
+
|
|
36
|
+
export function parseClaimListArgs(args: string[]): ParsedClaimListArgs;
|
|
37
|
+
|
|
38
|
+
export function renderClaimsTable(entries: ClaimEntry[]): string;
|
|
39
|
+
|
|
40
|
+
export function runClaimClaim(
|
|
41
|
+
args: string[],
|
|
42
|
+
options?: { openClaimLedger?: () => ClaimLedger },
|
|
43
|
+
): number;
|
|
44
|
+
|
|
45
|
+
export function runClaimRelease(
|
|
46
|
+
args: string[],
|
|
47
|
+
options?: { openClaimLedger?: () => ClaimLedger },
|
|
48
|
+
): number;
|
|
49
|
+
|
|
50
|
+
export function runClaimList(
|
|
51
|
+
args: string[],
|
|
52
|
+
options?: { openClaimLedger?: () => ClaimLedger },
|
|
53
|
+
): number;
|
|
54
|
+
|
|
55
|
+
export function runClaimCli(
|
|
56
|
+
subcommand: string | undefined,
|
|
57
|
+
args: string[],
|
|
58
|
+
options?: { openClaimLedger?: () => ClaimLedger },
|
|
59
|
+
): number;
|