@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,320 @@
|
|
|
1
|
+
import { CLAIM_STATUSES, openClaimLedger } from "./claim-ledger.js";
|
|
2
|
+
import { argsWantJson, describeCliError, reportCliFailure } from "./cli-error.js";
|
|
3
|
+
|
|
4
|
+
const CLAIM_CLAIM_USAGE =
|
|
5
|
+
"Usage: gittensory-miner claim claim <owner/repo> <issue#> [--note <text>] [--api-base-url <url>] [--dry-run] [--json]";
|
|
6
|
+
const CLAIM_RELEASE_USAGE =
|
|
7
|
+
"Usage: gittensory-miner claim release <owner/repo> <issue#> [--api-base-url <url>] [--dry-run] [--json]";
|
|
8
|
+
const CLAIM_LIST_USAGE =
|
|
9
|
+
"Usage: gittensory-miner claim list [--repo <owner/repo>] [--status active|released|expired] [--json]";
|
|
10
|
+
|
|
11
|
+
function parseRepoArg(value, usage) {
|
|
12
|
+
if (!value) return { error: usage };
|
|
13
|
+
const trimmed = value.trim();
|
|
14
|
+
const [owner, repo, extra] = trimmed.split("/");
|
|
15
|
+
if (!owner || !repo || extra !== undefined) {
|
|
16
|
+
return { error: "Repository must be in owner/repo form." };
|
|
17
|
+
}
|
|
18
|
+
return { repoFullName: `${owner}/${repo}` };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseIssueNumberArg(value, usage) {
|
|
22
|
+
if (!value) return { error: usage };
|
|
23
|
+
const parsed = Number(value);
|
|
24
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
25
|
+
return { error: "issue number must be a positive integer." };
|
|
26
|
+
}
|
|
27
|
+
return { issueNumber: parsed };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function parseClaimClaimArgs(args) {
|
|
31
|
+
const options = { json: false, note: undefined, dryRun: false, apiBaseUrl: undefined };
|
|
32
|
+
const positional = [];
|
|
33
|
+
|
|
34
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
35
|
+
const token = args[index];
|
|
36
|
+
if (token === "--json") {
|
|
37
|
+
options.json = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// #4847: reports what a real claim would do and returns before opening the claim ledger at all.
|
|
41
|
+
if (token === "--dry-run") {
|
|
42
|
+
options.dryRun = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (token === "--note") {
|
|
46
|
+
const note = args[index + 1];
|
|
47
|
+
if (!note || note.startsWith("-")) {
|
|
48
|
+
return { error: CLAIM_CLAIM_USAGE };
|
|
49
|
+
}
|
|
50
|
+
options.note = note;
|
|
51
|
+
index += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// #5563: scope the claim to a non-default forge host, so it doesn't collide with (or get confused for) a
|
|
55
|
+
// same-named repo on the default github.com host.
|
|
56
|
+
if (token === "--api-base-url") {
|
|
57
|
+
const value = args[index + 1];
|
|
58
|
+
if (!value || value.startsWith("-")) {
|
|
59
|
+
return { error: CLAIM_CLAIM_USAGE };
|
|
60
|
+
}
|
|
61
|
+
options.apiBaseUrl = value;
|
|
62
|
+
index += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (token.startsWith("-")) {
|
|
66
|
+
return { error: `Unknown option: ${token}` };
|
|
67
|
+
}
|
|
68
|
+
positional.push(token);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (positional.length !== 2) {
|
|
72
|
+
return { error: CLAIM_CLAIM_USAGE };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const repo = parseRepoArg(positional[0], CLAIM_CLAIM_USAGE);
|
|
76
|
+
if ("error" in repo) return repo;
|
|
77
|
+
const issue = parseIssueNumberArg(positional[1], CLAIM_CLAIM_USAGE);
|
|
78
|
+
if ("error" in issue) return issue;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
repoFullName: repo.repoFullName,
|
|
82
|
+
issueNumber: issue.issueNumber,
|
|
83
|
+
note: options.note,
|
|
84
|
+
dryRun: options.dryRun,
|
|
85
|
+
json: options.json,
|
|
86
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function parseClaimReleaseArgs(args) {
|
|
91
|
+
const options = { json: false, dryRun: false, apiBaseUrl: undefined };
|
|
92
|
+
const positional = [];
|
|
93
|
+
|
|
94
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
95
|
+
const token = args[index];
|
|
96
|
+
if (token === "--json") {
|
|
97
|
+
options.json = true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (token === "--dry-run") {
|
|
101
|
+
options.dryRun = true;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (token === "--api-base-url") {
|
|
105
|
+
const value = args[index + 1];
|
|
106
|
+
if (!value || value.startsWith("-")) {
|
|
107
|
+
return { error: CLAIM_RELEASE_USAGE };
|
|
108
|
+
}
|
|
109
|
+
options.apiBaseUrl = value;
|
|
110
|
+
index += 1;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (token.startsWith("-")) {
|
|
114
|
+
return { error: `Unknown option: ${token}` };
|
|
115
|
+
}
|
|
116
|
+
positional.push(token);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (positional.length !== 2) {
|
|
120
|
+
return { error: CLAIM_RELEASE_USAGE };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const repo = parseRepoArg(positional[0], CLAIM_RELEASE_USAGE);
|
|
124
|
+
if ("error" in repo) return repo;
|
|
125
|
+
const issue = parseIssueNumberArg(positional[1], CLAIM_RELEASE_USAGE);
|
|
126
|
+
if ("error" in issue) return issue;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
repoFullName: repo.repoFullName,
|
|
130
|
+
issueNumber: issue.issueNumber,
|
|
131
|
+
dryRun: options.dryRun,
|
|
132
|
+
json: options.json,
|
|
133
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function parseClaimListArgs(args) {
|
|
138
|
+
const options = { json: false, repoFullName: null, status: null };
|
|
139
|
+
const positional = [];
|
|
140
|
+
|
|
141
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
142
|
+
const token = args[index];
|
|
143
|
+
if (token === "--json") {
|
|
144
|
+
options.json = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (token === "--repo") {
|
|
148
|
+
const repoArg = args[index + 1];
|
|
149
|
+
if (!repoArg || repoArg.startsWith("-")) {
|
|
150
|
+
return { error: CLAIM_LIST_USAGE };
|
|
151
|
+
}
|
|
152
|
+
const repo = parseRepoArg(repoArg, CLAIM_LIST_USAGE);
|
|
153
|
+
if ("error" in repo) return repo;
|
|
154
|
+
options.repoFullName = repo.repoFullName;
|
|
155
|
+
index += 1;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (token === "--status") {
|
|
159
|
+
const statusArg = args[index + 1];
|
|
160
|
+
if (!statusArg || statusArg.startsWith("-")) {
|
|
161
|
+
return { error: CLAIM_LIST_USAGE };
|
|
162
|
+
}
|
|
163
|
+
if (!CLAIM_STATUSES.includes(statusArg)) {
|
|
164
|
+
return { error: `status must be one of: ${CLAIM_STATUSES.join(", ")}.` };
|
|
165
|
+
}
|
|
166
|
+
options.status = statusArg;
|
|
167
|
+
index += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (token.startsWith("-")) {
|
|
171
|
+
return { error: `Unknown option: ${token}` };
|
|
172
|
+
}
|
|
173
|
+
positional.push(token);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (positional.length > 0) {
|
|
177
|
+
return { error: CLAIM_LIST_USAGE };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return options;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function display(value) {
|
|
184
|
+
if (value === null || value === undefined) return "-";
|
|
185
|
+
return String(value);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function renderClaimsTable(entries) {
|
|
189
|
+
if (!Array.isArray(entries) || entries.length === 0) return "no claim ledger entries";
|
|
190
|
+
const header = [
|
|
191
|
+
"repo".padEnd(24),
|
|
192
|
+
"issue".padStart(6),
|
|
193
|
+
"status".padEnd(10),
|
|
194
|
+
"claimed-at".padEnd(24),
|
|
195
|
+
"note".padEnd(16),
|
|
196
|
+
].join(" ");
|
|
197
|
+
const lines = entries.map((entry) =>
|
|
198
|
+
[
|
|
199
|
+
entry.repoFullName.padEnd(24),
|
|
200
|
+
display(entry.issueNumber).padStart(6),
|
|
201
|
+
entry.status.padEnd(10),
|
|
202
|
+
display(entry.claimedAt).padEnd(24),
|
|
203
|
+
display(entry.note).padEnd(16),
|
|
204
|
+
].join(" "),
|
|
205
|
+
);
|
|
206
|
+
return [header, ...lines].join("\n");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function withClaimLedger(options, run) {
|
|
210
|
+
const ownsLedger = options.openClaimLedger === undefined;
|
|
211
|
+
const claimLedger = (options.openClaimLedger ?? openClaimLedger)();
|
|
212
|
+
try {
|
|
213
|
+
return run(claimLedger);
|
|
214
|
+
} finally {
|
|
215
|
+
if (ownsLedger) claimLedger.close();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function runClaimClaim(args, options = {}) {
|
|
220
|
+
const parsed = parseClaimClaimArgs(args);
|
|
221
|
+
if ("error" in parsed) {
|
|
222
|
+
return reportCliFailure(argsWantJson(args), parsed.error);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (parsed.dryRun) {
|
|
226
|
+
const dryRunResult = { outcome: "dry_run", repoFullName: parsed.repoFullName, issueNumber: parsed.issueNumber, note: parsed.note ?? null };
|
|
227
|
+
if (parsed.json) {
|
|
228
|
+
console.log(JSON.stringify(dryRunResult, null, 2));
|
|
229
|
+
} else {
|
|
230
|
+
console.log(
|
|
231
|
+
`DRY RUN: would claim ${parsed.repoFullName}#${parsed.issueNumber}${parsed.note ? ` (note: ${parsed.note})` : ""}. No claim-ledger write was made.`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
return withClaimLedger(options, (claimLedger) => {
|
|
239
|
+
const claim = claimLedger.claimIssue(
|
|
240
|
+
parsed.repoFullName,
|
|
241
|
+
parsed.issueNumber,
|
|
242
|
+
parsed.note,
|
|
243
|
+
parsed.apiBaseUrl,
|
|
244
|
+
);
|
|
245
|
+
if (parsed.json) {
|
|
246
|
+
console.log(JSON.stringify({ claim }, null, 2));
|
|
247
|
+
} else {
|
|
248
|
+
console.log(claim.status);
|
|
249
|
+
}
|
|
250
|
+
return 0;
|
|
251
|
+
});
|
|
252
|
+
} catch (error) {
|
|
253
|
+
return reportCliFailure(parsed.json, describeCliError(error));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function runClaimRelease(args, options = {}) {
|
|
258
|
+
const parsed = parseClaimReleaseArgs(args);
|
|
259
|
+
if ("error" in parsed) {
|
|
260
|
+
return reportCliFailure(argsWantJson(args), parsed.error);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (parsed.dryRun) {
|
|
264
|
+
const dryRunResult = { outcome: "dry_run", repoFullName: parsed.repoFullName, issueNumber: parsed.issueNumber };
|
|
265
|
+
if (parsed.json) {
|
|
266
|
+
console.log(JSON.stringify(dryRunResult, null, 2));
|
|
267
|
+
} else {
|
|
268
|
+
console.log(`DRY RUN: would release the claim on ${parsed.repoFullName}#${parsed.issueNumber}. No claim-ledger write was made.`);
|
|
269
|
+
}
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
return withClaimLedger(options, (claimLedger) => {
|
|
275
|
+
const claim = claimLedger.releaseClaim(parsed.repoFullName, parsed.issueNumber, parsed.apiBaseUrl);
|
|
276
|
+
if (!claim) {
|
|
277
|
+
return reportCliFailure(parsed.json, "claim_not_found");
|
|
278
|
+
}
|
|
279
|
+
if (parsed.json) {
|
|
280
|
+
console.log(JSON.stringify({ claim }, null, 2));
|
|
281
|
+
} else {
|
|
282
|
+
console.log(claim.status);
|
|
283
|
+
}
|
|
284
|
+
return 0;
|
|
285
|
+
});
|
|
286
|
+
} catch (error) {
|
|
287
|
+
return reportCliFailure(parsed.json, describeCliError(error));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function runClaimList(args, options = {}) {
|
|
292
|
+
const parsed = parseClaimListArgs(args);
|
|
293
|
+
if ("error" in parsed) {
|
|
294
|
+
return reportCliFailure(argsWantJson(args), parsed.error);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
return withClaimLedger(options, (claimLedger) => {
|
|
299
|
+
const filter = {};
|
|
300
|
+
if (parsed.repoFullName !== null) filter.repoFullName = parsed.repoFullName;
|
|
301
|
+
if (parsed.status !== null) filter.status = parsed.status;
|
|
302
|
+
const claims = claimLedger.listClaims(filter);
|
|
303
|
+
if (parsed.json) {
|
|
304
|
+
console.log(JSON.stringify({ claims }, null, 2));
|
|
305
|
+
} else {
|
|
306
|
+
console.log(renderClaimsTable(claims));
|
|
307
|
+
}
|
|
308
|
+
return 0;
|
|
309
|
+
});
|
|
310
|
+
} catch (error) {
|
|
311
|
+
return reportCliFailure(parsed.json, describeCliError(error));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function runClaimCli(subcommand, args, options = {}) {
|
|
316
|
+
if (subcommand === "claim") return runClaimClaim(args, options);
|
|
317
|
+
if (subcommand === "release") return runClaimRelease(args, options);
|
|
318
|
+
if (subcommand === "list") return runClaimList(args, options);
|
|
319
|
+
return reportCliFailure(argsWantJson(args), `Unknown claim subcommand: ${subcommand ?? ""}. ${CLAIM_LIST_USAGE}`);
|
|
320
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ClaimEntry } from "./claim-ledger.js";
|
|
2
|
+
|
|
3
|
+
export declare const DEFAULT_MAX_CLAIM_AGE_MS: number;
|
|
4
|
+
|
|
5
|
+
export type ClaimLedgerExpiryStore = {
|
|
6
|
+
listClaims(filter?: { status?: "active" }): ClaimEntry[];
|
|
7
|
+
expireClaim(repoFullName: string, issueNumber: number): ClaimEntry | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function findExpiredClaims(
|
|
11
|
+
claims: ClaimEntry[],
|
|
12
|
+
nowMs: number,
|
|
13
|
+
maxAgeMs: number,
|
|
14
|
+
): ClaimEntry[];
|
|
15
|
+
|
|
16
|
+
export function sweepExpiredClaims(
|
|
17
|
+
store: ClaimLedgerExpiryStore,
|
|
18
|
+
nowMs: number,
|
|
19
|
+
maxAgeMs?: number,
|
|
20
|
+
): ClaimEntry[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** PURE — no IO, no Date, no random (#2316). */
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_MAX_CLAIM_AGE_MS = 14 * 24 * 60 * 60 * 1000;
|
|
4
|
+
|
|
5
|
+
function claimAgeMs(claim, nowMs) {
|
|
6
|
+
const claimedAtMs = Date.parse(claim.claimedAt);
|
|
7
|
+
if (!Number.isFinite(claimedAtMs)) return null;
|
|
8
|
+
return nowMs - claimedAtMs;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Return active claims whose age is strictly greater than `maxAgeMs`. A claim whose age equals `maxAgeMs` exactly
|
|
13
|
+
* is still considered within the window (not expired).
|
|
14
|
+
*/
|
|
15
|
+
export function findExpiredClaims(claims, nowMs, maxAgeMs) {
|
|
16
|
+
if (!Number.isFinite(nowMs) || nowMs < 0) throw new Error("invalid_now_ms");
|
|
17
|
+
if (!Number.isFinite(maxAgeMs) || maxAgeMs < 0) throw new Error("invalid_max_age_ms");
|
|
18
|
+
if (!Array.isArray(claims)) throw new Error("invalid_claims");
|
|
19
|
+
|
|
20
|
+
const expired = [];
|
|
21
|
+
for (const claim of claims) {
|
|
22
|
+
if (claim?.status !== "active") continue;
|
|
23
|
+
const ageMs = claimAgeMs(claim, nowMs);
|
|
24
|
+
if (ageMs === null) continue;
|
|
25
|
+
if (ageMs > maxAgeMs) expired.push(claim);
|
|
26
|
+
}
|
|
27
|
+
return expired;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function sweepExpiredClaims(store, nowMs, maxAgeMs = DEFAULT_MAX_CLAIM_AGE_MS) {
|
|
31
|
+
const activeClaims = store.listClaims({ status: "active" });
|
|
32
|
+
const expired = findExpiredClaims(activeClaims, nowMs, maxAgeMs);
|
|
33
|
+
const transitioned = [];
|
|
34
|
+
for (const claim of expired) {
|
|
35
|
+
// Echo the row's OWN apiBaseUrl back (#5563) rather than defaulting: two forge hosts can each have an
|
|
36
|
+
// active claim on the same owner/repo#issue, and defaulting here would expire the wrong host's row.
|
|
37
|
+
const updated = store.expireClaim(claim.repoFullName, claim.issueNumber, claim.apiBaseUrl);
|
|
38
|
+
if (updated) transitioned.push(updated);
|
|
39
|
+
}
|
|
40
|
+
return transitioned;
|
|
41
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type ClaimStatus = "active" | "released" | "expired";
|
|
2
|
+
|
|
3
|
+
export type ClaimEntry = {
|
|
4
|
+
id: number;
|
|
5
|
+
apiBaseUrl: string;
|
|
6
|
+
repoFullName: string;
|
|
7
|
+
issueNumber: number;
|
|
8
|
+
claimedAt: string;
|
|
9
|
+
status: ClaimStatus;
|
|
10
|
+
note: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type RecordClaimInput = {
|
|
14
|
+
repoFullName: string;
|
|
15
|
+
issueNumber: number;
|
|
16
|
+
note?: string;
|
|
17
|
+
apiBaseUrl?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ListClaimsFilter = {
|
|
21
|
+
repoFullName?: string | null;
|
|
22
|
+
status?: ClaimStatus | null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type ClaimLedger = {
|
|
26
|
+
dbPath: string;
|
|
27
|
+
recordClaim(claim: RecordClaimInput): ClaimEntry;
|
|
28
|
+
claimIssue(repoFullName: string, issueNumber: number, note?: string, apiBaseUrl?: string): ClaimEntry;
|
|
29
|
+
releaseClaim(repoFullName: string, issueNumber: number, apiBaseUrl?: string): ClaimEntry | null;
|
|
30
|
+
expireClaim(repoFullName: string, issueNumber: number, apiBaseUrl?: string): ClaimEntry | null;
|
|
31
|
+
listClaims(filter?: ListClaimsFilter): ClaimEntry[];
|
|
32
|
+
listActiveClaims(repoFullName?: string): ClaimEntry[];
|
|
33
|
+
purgeByRepo(repoFullName: string): number;
|
|
34
|
+
close(): void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const CLAIM_STATUSES: readonly ClaimStatus[];
|
|
38
|
+
|
|
39
|
+
export function resolveClaimLedgerDbPath(env?: Record<string, string | undefined>): string;
|
|
40
|
+
|
|
41
|
+
export function openClaimLedger(dbPath?: string): ClaimLedger;
|
|
42
|
+
|
|
43
|
+
export type ReadOnlyClaimLedger = {
|
|
44
|
+
dbPath: string;
|
|
45
|
+
listActiveClaims(repoFullName: string): ClaimEntry[];
|
|
46
|
+
close(): void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function openClaimLedgerReadOnly(dbPath: string): ReadOnlyClaimLedger;
|
|
50
|
+
|
|
51
|
+
export function recordClaim(claim: RecordClaimInput): ClaimEntry;
|
|
52
|
+
|
|
53
|
+
export function releaseClaim(repoFullName: string, issueNumber: number, apiBaseUrl?: string): ClaimEntry | null;
|
|
54
|
+
|
|
55
|
+
export function expireClaim(repoFullName: string, issueNumber: number, apiBaseUrl?: string): ClaimEntry | null;
|
|
56
|
+
|
|
57
|
+
export function listClaims(filter?: ListClaimsFilter): ClaimEntry[];
|
|
58
|
+
|
|
59
|
+
export function claimIssue(repoFullName: string, issueNumber: number, note?: string, apiBaseUrl?: string): ClaimEntry;
|
|
60
|
+
|
|
61
|
+
export function listActiveClaims(repoFullName?: string): ClaimEntry[];
|
|
62
|
+
|
|
63
|
+
export function closeDefaultClaimLedger(): void;
|