terminalhire 0.26.2 → 0.28.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/dist/bin/jpi-claim.js +279 -10
- package/dist/bin/jpi-dispatch.js +281 -11
- package/package.json +1 -1
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -8882,6 +8882,10 @@ ${p.body ?? ""}`)) {
|
|
|
8882
8882
|
}
|
|
8883
8883
|
return matched;
|
|
8884
8884
|
}
|
|
8885
|
+
function selectCompetingPrs(prs, selfLogin) {
|
|
8886
|
+
if (!Array.isArray(prs)) return [];
|
|
8887
|
+
return prs.filter((pr) => pr && pr.author !== selfLogin);
|
|
8888
|
+
}
|
|
8885
8889
|
async function listOpenPRsReferencingIssue(repoFullName, issueNumber) {
|
|
8886
8890
|
try {
|
|
8887
8891
|
const res = await fetch(`${GH_API2}/repos/${repoFullName}/pulls?state=open&per_page=100`, {
|
|
@@ -8947,6 +8951,67 @@ async function fetchIssue(repoFullName, issueNumber) {
|
|
|
8947
8951
|
return null;
|
|
8948
8952
|
}
|
|
8949
8953
|
}
|
|
8954
|
+
var ASSIGNMENT_MARKER = "<!-- terminalhire:assignment-request -->";
|
|
8955
|
+
var TAKE_BOT_REPOS = /* @__PURE__ */ new Set(["paradedb/paradedb"]);
|
|
8956
|
+
function buildAssignmentComment(repoFullName) {
|
|
8957
|
+
const usesTakeBot = TAKE_BOT_REPOS.has(repoFullName.toLowerCase());
|
|
8958
|
+
const body = usesTakeBot ? "/take" : `I'd like to work on this \u2014 could I be assigned? Thanks!
|
|
8959
|
+
|
|
8960
|
+
${ASSIGNMENT_MARKER}`;
|
|
8961
|
+
return { usesTakeBot, body };
|
|
8962
|
+
}
|
|
8963
|
+
async function hasPriorAssignmentRequest(repoFullName, issueNumber, login) {
|
|
8964
|
+
try {
|
|
8965
|
+
const res = await fetch(
|
|
8966
|
+
`${GH_API2}/repos/${repoFullName}/issues/${issueNumber}/comments?per_page=100&sort=created&direction=desc`,
|
|
8967
|
+
{ headers: GH_HEADERS2, signal: AbortSignal.timeout(1e4) }
|
|
8968
|
+
);
|
|
8969
|
+
if (!res.ok) return false;
|
|
8970
|
+
const comments = await res.json();
|
|
8971
|
+
if (!Array.isArray(comments)) return false;
|
|
8972
|
+
return comments.some(
|
|
8973
|
+
(c) => c && c.user && c.user.login === login && typeof c.body === "string" && c.body.includes(ASSIGNMENT_MARKER)
|
|
8974
|
+
);
|
|
8975
|
+
} catch {
|
|
8976
|
+
return false;
|
|
8977
|
+
}
|
|
8978
|
+
}
|
|
8979
|
+
async function requestIssueAssignment(claim, flags = {}, ghUser) {
|
|
8980
|
+
if (flags["no-assign"]) {
|
|
8981
|
+
console.log(" (--no-assign \u2014 skipping the assignment request; request it manually before working)");
|
|
8982
|
+
return;
|
|
8983
|
+
}
|
|
8984
|
+
const parsed = parseGitHubUrl(claim.issueUrl);
|
|
8985
|
+
if (!parsed) return;
|
|
8986
|
+
const { repoFullName, number } = parsed;
|
|
8987
|
+
let login = ghUser;
|
|
8988
|
+
if (!login) {
|
|
8989
|
+
try {
|
|
8990
|
+
login = await sh("gh", ["api", "user", "-q", ".login"]);
|
|
8991
|
+
} catch {
|
|
8992
|
+
console.log(" (assignment not requested: 'gh' not authenticated \u2014 comment on the issue manually before working)");
|
|
8993
|
+
return;
|
|
8994
|
+
}
|
|
8995
|
+
}
|
|
8996
|
+
const issue = await fetchIssue(repoFullName, number);
|
|
8997
|
+
if (issue && issue.assignees.includes(login)) {
|
|
8998
|
+
console.log(` \u2713 Already assigned to @${login} on ${repoFullName}#${number}.`);
|
|
8999
|
+
return;
|
|
9000
|
+
}
|
|
9001
|
+
const { usesTakeBot, body } = buildAssignmentComment(repoFullName);
|
|
9002
|
+
if (!usesTakeBot && await hasPriorAssignmentRequest(repoFullName, number, login)) {
|
|
9003
|
+
console.log(` \u2713 Assignment already requested on ${repoFullName}#${number}.`);
|
|
9004
|
+
return;
|
|
9005
|
+
}
|
|
9006
|
+
try {
|
|
9007
|
+
await sh("gh", ["issue", "comment", String(number), "--repo", repoFullName, "--body", body]);
|
|
9008
|
+
console.log(
|
|
9009
|
+
usesTakeBot ? ` \u2713 Requested assignment via /take on ${repoFullName}#${number}.` : ` \u2713 Requested assignment on ${repoFullName}#${number}.`
|
|
9010
|
+
);
|
|
9011
|
+
} catch (err) {
|
|
9012
|
+
console.log(` (could not post the assignment request: ${err.stderr || err.message || err} \u2014 do it manually before working)`);
|
|
9013
|
+
}
|
|
9014
|
+
}
|
|
8950
9015
|
async function pollPR(prUrl) {
|
|
8951
9016
|
const p = parseGitHubUrl(prUrl);
|
|
8952
9017
|
if (!p) return null;
|
|
@@ -9162,10 +9227,19 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
|
|
|
9162
9227
|
console.log(" \u2022 MUST NOT `git push` or `gh pr` \u2014 pushing happens only via `terminalhire submit`");
|
|
9163
9228
|
console.log(" \u2022 clone + static analysis + patch only; NO test/build execution without explicit approval");
|
|
9164
9229
|
console.log(" \u2022 no access to ~/.terminalhire (the executor never needs your profile)");
|
|
9165
|
-
console.log("\n Next:");
|
|
9166
|
-
console.log("
|
|
9167
|
-
console.log("
|
|
9168
|
-
console.log("
|
|
9230
|
+
console.log("\n Next \u2014 start work (forks + clones into an isolated worktree; your terminal stays put):");
|
|
9231
|
+
console.log(" terminalhire claim start " + claim.id);
|
|
9232
|
+
console.log(" Then publish when it is done (the only step that pushes + opens the PR):");
|
|
9233
|
+
console.log(" terminalhire claim submit " + claim.id);
|
|
9234
|
+
if (flags.start) {
|
|
9235
|
+
await cmdStart(claim.id, { start: true });
|
|
9236
|
+
} else if (process.stdin.isTTY && !flags["no-start"]) {
|
|
9237
|
+
const go = await confirm(`
|
|
9238
|
+
Fork ${claim.repoFullName} and start now? (y/N) `);
|
|
9239
|
+
if (go) await cmdStart(claim.id, { start: true });
|
|
9240
|
+
else console.log(`
|
|
9241
|
+
Saved. Start anytime: terminalhire claim start ${claim.id}`);
|
|
9242
|
+
}
|
|
9169
9243
|
}
|
|
9170
9244
|
async function cmdPreview(arg, { json } = {}) {
|
|
9171
9245
|
if (!arg) {
|
|
@@ -9362,6 +9436,181 @@ async function cmdAttach(id, worktree, branch) {
|
|
|
9362
9436
|
claims.updateClaim(id, { worktreePath: toplevel, branch });
|
|
9363
9437
|
console.log(`Attached ${id}: worktree=${toplevel} branch=${branch}`);
|
|
9364
9438
|
}
|
|
9439
|
+
function workDirFor(repoFullName, issueNumber) {
|
|
9440
|
+
const [owner, repo] = String(repoFullName).split("/");
|
|
9441
|
+
const suffix = issueNumber ? `-${issueNumber}` : "";
|
|
9442
|
+
return join7(homedir6(), "terminalhire", "work", `${owner}-${repo}${suffix}`);
|
|
9443
|
+
}
|
|
9444
|
+
function startBranchFor(repoFullName, issueNumber) {
|
|
9445
|
+
const repo = String(repoFullName).split("/")[1] || "claim";
|
|
9446
|
+
return `th/${repo}-${issueNumber || "work"}`;
|
|
9447
|
+
}
|
|
9448
|
+
function explicitForkConsent(flags = {}) {
|
|
9449
|
+
return Boolean(flags.start) || Boolean(flags.fork);
|
|
9450
|
+
}
|
|
9451
|
+
async function ensureForkExists(repoFullName, ghUser) {
|
|
9452
|
+
const repoShort = repoFullName.split("/")[1];
|
|
9453
|
+
const forkFullName = `${ghUser}/${repoShort}`;
|
|
9454
|
+
await sh("gh", ["repo", "fork", repoFullName, "--clone=false"]);
|
|
9455
|
+
let isFork = false;
|
|
9456
|
+
try {
|
|
9457
|
+
isFork = await sh("gh", ["api", `repos/${forkFullName}`, "--jq", ".fork"]) === "true";
|
|
9458
|
+
} catch {
|
|
9459
|
+
isFork = false;
|
|
9460
|
+
}
|
|
9461
|
+
if (!isFork) throw new Error(`fork ${forkFullName} created but could not be verified as a fork`);
|
|
9462
|
+
return forkFullName;
|
|
9463
|
+
}
|
|
9464
|
+
async function cmdStart(id, flags = {}) {
|
|
9465
|
+
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
9466
|
+
if (!id) {
|
|
9467
|
+
const startable = claims.listClaims({ active: true }).filter((c) => c.state === "claimed");
|
|
9468
|
+
if (startable.length === 0) {
|
|
9469
|
+
console.log("No claims ready to start. Claim one first: terminalhire claim <ref>");
|
|
9470
|
+
return;
|
|
9471
|
+
}
|
|
9472
|
+
console.log(`
|
|
9473
|
+
${startable.length} claim${startable.length === 1 ? "" : "s"} ready to start:
|
|
9474
|
+
`);
|
|
9475
|
+
for (const c of startable) {
|
|
9476
|
+
console.log(` ${c.title}`);
|
|
9477
|
+
console.log(` terminalhire claim start ${c.id}`);
|
|
9478
|
+
}
|
|
9479
|
+
console.log("\nRun the command under the one you want to start.");
|
|
9480
|
+
return;
|
|
9481
|
+
}
|
|
9482
|
+
const claim = claims.findClaim(id);
|
|
9483
|
+
if (!claim) {
|
|
9484
|
+
console.error(`terminalhire claim: no claim with id '${id}'.`);
|
|
9485
|
+
process.exit(1);
|
|
9486
|
+
}
|
|
9487
|
+
if (claim.worktreePath) {
|
|
9488
|
+
let stillThere = false;
|
|
9489
|
+
try {
|
|
9490
|
+
stillThere = await sh("git", ["-C", claim.worktreePath, "rev-parse", "--show-toplevel"]) === claim.worktreePath;
|
|
9491
|
+
} catch {
|
|
9492
|
+
stillThere = false;
|
|
9493
|
+
}
|
|
9494
|
+
if (stillThere) {
|
|
9495
|
+
console.log("Already started \u2014 your worktree is ready (your terminal stays put):");
|
|
9496
|
+
console.log(` cd ${claim.worktreePath}`);
|
|
9497
|
+
if (claim.branch) console.log(` branch: ${claim.branch}`);
|
|
9498
|
+
console.log(`
|
|
9499
|
+
When it's done: terminalhire claim submit ${id}`);
|
|
9500
|
+
return;
|
|
9501
|
+
}
|
|
9502
|
+
}
|
|
9503
|
+
if (flags.here) {
|
|
9504
|
+
await cmdStartHere(claims, claim, flags);
|
|
9505
|
+
return;
|
|
9506
|
+
}
|
|
9507
|
+
let ghUser;
|
|
9508
|
+
try {
|
|
9509
|
+
ghUser = await sh("gh", ["api", "user", "-q", ".login"]);
|
|
9510
|
+
} catch {
|
|
9511
|
+
console.error("terminalhire claim: 'gh' CLI not available or not authenticated. Run 'gh auth login'.");
|
|
9512
|
+
process.exit(1);
|
|
9513
|
+
}
|
|
9514
|
+
let consented = explicitForkConsent(flags);
|
|
9515
|
+
if (!consented && process.stdin.isTTY) {
|
|
9516
|
+
consented = await confirm(`
|
|
9517
|
+
Fork ${claim.repoFullName} to @${ghUser} and clone it to start? (y/N) `);
|
|
9518
|
+
}
|
|
9519
|
+
if (!consented) {
|
|
9520
|
+
console.error(
|
|
9521
|
+
`
|
|
9522
|
+
terminalhire claim: not started \u2014 starting forks ${claim.repoFullName} to your GitHub account (a network write).
|
|
9523
|
+
Re-run in a terminal to confirm interactively, or pass --fork to consent non-interactively.`
|
|
9524
|
+
);
|
|
9525
|
+
process.exit(1);
|
|
9526
|
+
}
|
|
9527
|
+
const issueNumber = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
9528
|
+
const destDir = workDirFor(claim.repoFullName, issueNumber);
|
|
9529
|
+
if (existsSync6(destDir)) {
|
|
9530
|
+
console.error(
|
|
9531
|
+
`terminalhire claim: ${destDir} already exists \u2014 refusing to clobber it.
|
|
9532
|
+
Remove it and retry, or attach it: terminalhire claim attach ${id} --worktree ${destDir} --branch <branch>`
|
|
9533
|
+
);
|
|
9534
|
+
process.exit(1);
|
|
9535
|
+
}
|
|
9536
|
+
mkdirSync6(join7(homedir6(), "terminalhire", "work"), { recursive: true });
|
|
9537
|
+
let forkFullName;
|
|
9538
|
+
try {
|
|
9539
|
+
forkFullName = await ensureForkExists(claim.repoFullName, ghUser);
|
|
9540
|
+
} catch (err) {
|
|
9541
|
+
console.error(`terminalhire claim: could not create your fork of ${claim.repoFullName}. ${err.message ?? err}`);
|
|
9542
|
+
process.exit(1);
|
|
9543
|
+
}
|
|
9544
|
+
try {
|
|
9545
|
+
await sh("gh", ["repo", "clone", forkFullName, destDir]);
|
|
9546
|
+
} catch (err) {
|
|
9547
|
+
try {
|
|
9548
|
+
rmSync3(destDir, { recursive: true, force: true });
|
|
9549
|
+
} catch {
|
|
9550
|
+
}
|
|
9551
|
+
console.error(`terminalhire claim: clone of ${forkFullName} failed. ${err.stderr || err.message || err}`);
|
|
9552
|
+
process.exit(1);
|
|
9553
|
+
}
|
|
9554
|
+
try {
|
|
9555
|
+
await sh("git", ["-C", destDir, "remote", "get-url", "upstream"]);
|
|
9556
|
+
} catch {
|
|
9557
|
+
try {
|
|
9558
|
+
await sh("git", ["-C", destDir, "remote", "add", "upstream", `https://github.com/${claim.repoFullName}.git`]);
|
|
9559
|
+
} catch {
|
|
9560
|
+
}
|
|
9561
|
+
}
|
|
9562
|
+
const branch = startBranchFor(claim.repoFullName, issueNumber);
|
|
9563
|
+
await sh("git", ["-C", destDir, "checkout", "-b", branch]);
|
|
9564
|
+
const toplevel = await sh("git", ["-C", destDir, "rev-parse", "--show-toplevel"]);
|
|
9565
|
+
claims.updateClaim(id, { worktreePath: toplevel, branch, state: "working" });
|
|
9566
|
+
await requestIssueAssignment(claim, flags, ghUser);
|
|
9567
|
+
console.log(`
|
|
9568
|
+
\u2713 Started: ${claim.title}`);
|
|
9569
|
+
console.log(` fork: ${forkFullName}`);
|
|
9570
|
+
console.log(` branch: ${branch}`);
|
|
9571
|
+
console.log("\n Your isolated worktree is ready \u2014 your current terminal is untouched:");
|
|
9572
|
+
console.log(` cd ${toplevel}`);
|
|
9573
|
+
console.log("\n When the work is done (the only step that pushes + opens the PR):");
|
|
9574
|
+
console.log(` terminalhire claim submit ${id}`);
|
|
9575
|
+
}
|
|
9576
|
+
async function cmdStartHere(claims, claim, flags = {}) {
|
|
9577
|
+
let toplevel;
|
|
9578
|
+
try {
|
|
9579
|
+
toplevel = await sh("git", ["-C", process.cwd(), "rev-parse", "--show-toplevel"]);
|
|
9580
|
+
} catch {
|
|
9581
|
+
console.error("terminalhire claim: --here must be run inside a git repository.");
|
|
9582
|
+
process.exit(1);
|
|
9583
|
+
}
|
|
9584
|
+
const remotesOut = await sh("git", ["-C", toplevel, "remote", "-v"]).catch(() => "");
|
|
9585
|
+
const repos = new Set(
|
|
9586
|
+
remotesOut.split("\n").map((l) => parseRepoFromRemote(l.split(/\s+/)[1])).filter(Boolean).map((r) => r.toLowerCase())
|
|
9587
|
+
);
|
|
9588
|
+
if (!repos.has(claim.repoFullName.toLowerCase())) {
|
|
9589
|
+
console.error(
|
|
9590
|
+
`terminalhire claim: --here expects a clone of ${claim.repoFullName}, but no remote here points there.
|
|
9591
|
+
Use \`terminalhire claim start ${claim.id}\` (no --here) to fork + clone it into a fresh worktree.`
|
|
9592
|
+
);
|
|
9593
|
+
process.exit(1);
|
|
9594
|
+
}
|
|
9595
|
+
const dirty = await sh("git", ["-C", toplevel, "status", "--porcelain", "--untracked-files=no"]).catch(() => "");
|
|
9596
|
+
if (dirty) {
|
|
9597
|
+
console.error(
|
|
9598
|
+
"terminalhire claim: --here needs a clean working tree (claim work must not mix with your current changes).\n Commit or stash first, or use `terminalhire claim start` for an isolated worktree."
|
|
9599
|
+
);
|
|
9600
|
+
process.exit(1);
|
|
9601
|
+
}
|
|
9602
|
+
const issueNumber = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
9603
|
+
const branch = startBranchFor(claim.repoFullName, issueNumber);
|
|
9604
|
+
await sh("git", ["-C", toplevel, "checkout", "-b", branch]);
|
|
9605
|
+
claims.updateClaim(claim.id, { worktreePath: toplevel, branch, state: "working" });
|
|
9606
|
+
await requestIssueAssignment(claim, flags);
|
|
9607
|
+
console.log(`
|
|
9608
|
+
\u2713 Started here: ${claim.title}`);
|
|
9609
|
+
console.log(` worktree: ${toplevel}`);
|
|
9610
|
+
console.log(` branch: ${branch}`);
|
|
9611
|
+
console.log(`
|
|
9612
|
+
When the work is done: terminalhire claim submit ${claim.id}`);
|
|
9613
|
+
}
|
|
9365
9614
|
async function cmdSubmit(id, flags = {}) {
|
|
9366
9615
|
const worktreeOverride = flags.worktree;
|
|
9367
9616
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
@@ -9376,10 +9625,10 @@ async function cmdSubmit(id, flags = {}) {
|
|
|
9376
9625
|
console.error(`terminalhire claim: no claim with id '${id}'.`);
|
|
9377
9626
|
process.exit(1);
|
|
9378
9627
|
}
|
|
9379
|
-
if (claim.state !== "ready") {
|
|
9628
|
+
if (claim.state !== "working" && claim.state !== "ready") {
|
|
9380
9629
|
console.error(
|
|
9381
|
-
`terminalhire claim: ${id} is '${claim.state}'
|
|
9382
|
-
terminalhire claim
|
|
9630
|
+
`terminalhire claim: ${id} is '${claim.state}'. Submit runs once work has started ('working', via 'claim start') or the review gate cleared it ('ready'). Start it first:
|
|
9631
|
+
terminalhire claim start ${id}`
|
|
9383
9632
|
);
|
|
9384
9633
|
process.exit(1);
|
|
9385
9634
|
}
|
|
@@ -9586,10 +9835,22 @@ async function cmdSubmit(id, flags = {}) {
|
|
|
9586
9835
|
const issueNo = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
9587
9836
|
const contention = issueNo ? await listOpenPRsReferencingIssue(claim.repoFullName, issueNo) : null;
|
|
9588
9837
|
if (contention && contention.total > 0) {
|
|
9589
|
-
console.log(` \u26A0 contention: ${contention.total} open PR(s) already reference this issue:`);
|
|
9590
9838
|
const nowMs = Date.now();
|
|
9839
|
+
console.log(` \u26A0 contention: ${contention.total} open PR(s) already reference this issue:`);
|
|
9591
9840
|
for (const pr of contention.prs) console.log(fmtContentionPr(pr, nowMs, false));
|
|
9592
9841
|
console.log(CONTENTION_HINT);
|
|
9842
|
+
const competing = selectCompetingPrs(contention.prs, ghUser);
|
|
9843
|
+
if (competing.length > 0 && !flags["force-competing"]) {
|
|
9844
|
+
console.error(
|
|
9845
|
+
`
|
|
9846
|
+
terminalhire claim: refusing to submit \u2014 ${competing.length} open PR(s) by someone else already address ${claim.repoFullName}#${issueNo}:`
|
|
9847
|
+
);
|
|
9848
|
+
for (const pr of competing) console.error(fmtContentionPr(pr, nowMs, false));
|
|
9849
|
+
console.error(
|
|
9850
|
+
"\n Opening a competing PR reads as low-effort duplication and burns maintainer goodwill.\n Stand down, or add value on the existing PR instead (a review, a test, a comment).\n If you are certain yours should still go up, re-run with --force-competing."
|
|
9851
|
+
);
|
|
9852
|
+
process.exit(1);
|
|
9853
|
+
}
|
|
9593
9854
|
}
|
|
9594
9855
|
}
|
|
9595
9856
|
let ok;
|
|
@@ -9971,6 +10232,9 @@ async function run() {
|
|
|
9971
10232
|
case "record":
|
|
9972
10233
|
await cmdRecord(positional[0], flags);
|
|
9973
10234
|
break;
|
|
10235
|
+
case "start":
|
|
10236
|
+
await cmdStart(positional[0], flags);
|
|
10237
|
+
break;
|
|
9974
10238
|
case "list":
|
|
9975
10239
|
await cmdList(active);
|
|
9976
10240
|
break;
|
|
@@ -9990,7 +10254,7 @@ async function run() {
|
|
|
9990
10254
|
await cmdRelease(positional[0]);
|
|
9991
10255
|
break;
|
|
9992
10256
|
default:
|
|
9993
|
-
console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | attach | list | status | update | submit | release`);
|
|
10257
|
+
console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | start | attach | list | status | update | submit | release`);
|
|
9994
10258
|
process.exit(1);
|
|
9995
10259
|
}
|
|
9996
10260
|
} catch (err) {
|
|
@@ -10002,10 +10266,12 @@ export {
|
|
|
10002
10266
|
AI_DISCLOSURE_NOTE,
|
|
10003
10267
|
CLAIM_CONSENT_VERSION,
|
|
10004
10268
|
backgroundEnableFailed,
|
|
10269
|
+
buildAssignmentComment,
|
|
10005
10270
|
buildSubmitBody,
|
|
10006
10271
|
cmdRecord,
|
|
10007
10272
|
countOpenPRsReferencingIssue,
|
|
10008
10273
|
diffContention,
|
|
10274
|
+
explicitForkConsent,
|
|
10009
10275
|
findClaimableByShortRef,
|
|
10010
10276
|
findClaimableInCache,
|
|
10011
10277
|
fmtAge,
|
|
@@ -10021,7 +10287,10 @@ export {
|
|
|
10021
10287
|
resolveSubmitWorktree,
|
|
10022
10288
|
revokeFailureAction,
|
|
10023
10289
|
run,
|
|
10024
|
-
|
|
10290
|
+
selectCompetingPrs,
|
|
10291
|
+
selectPushRemote,
|
|
10292
|
+
startBranchFor,
|
|
10293
|
+
workDirFor
|
|
10025
10294
|
};
|
|
10026
10295
|
/*! Bundled license information:
|
|
10027
10296
|
|
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -11035,10 +11035,12 @@ __export(jpi_claim_exports, {
|
|
|
11035
11035
|
AI_DISCLOSURE_NOTE: () => AI_DISCLOSURE_NOTE,
|
|
11036
11036
|
CLAIM_CONSENT_VERSION: () => CLAIM_CONSENT_VERSION,
|
|
11037
11037
|
backgroundEnableFailed: () => backgroundEnableFailed,
|
|
11038
|
+
buildAssignmentComment: () => buildAssignmentComment,
|
|
11038
11039
|
buildSubmitBody: () => buildSubmitBody,
|
|
11039
11040
|
cmdRecord: () => cmdRecord,
|
|
11040
11041
|
countOpenPRsReferencingIssue: () => countOpenPRsReferencingIssue,
|
|
11041
11042
|
diffContention: () => diffContention,
|
|
11043
|
+
explicitForkConsent: () => explicitForkConsent,
|
|
11042
11044
|
findClaimableByShortRef: () => findClaimableByShortRef,
|
|
11043
11045
|
findClaimableInCache: () => findClaimableInCache,
|
|
11044
11046
|
fmtAge: () => fmtAge,
|
|
@@ -11054,7 +11056,10 @@ __export(jpi_claim_exports, {
|
|
|
11054
11056
|
resolveSubmitWorktree: () => resolveSubmitWorktree,
|
|
11055
11057
|
revokeFailureAction: () => revokeFailureAction,
|
|
11056
11058
|
run: () => run7,
|
|
11057
|
-
|
|
11059
|
+
selectCompetingPrs: () => selectCompetingPrs,
|
|
11060
|
+
selectPushRemote: () => selectPushRemote,
|
|
11061
|
+
startBranchFor: () => startBranchFor,
|
|
11062
|
+
workDirFor: () => workDirFor
|
|
11058
11063
|
});
|
|
11059
11064
|
import { readFileSync as readFileSync17, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, existsSync as existsSync10, rmSync as rmSync4 } from "fs";
|
|
11060
11065
|
import { join as join17 } from "path";
|
|
@@ -11285,6 +11290,10 @@ ${p.body ?? ""}`)) {
|
|
|
11285
11290
|
}
|
|
11286
11291
|
return matched;
|
|
11287
11292
|
}
|
|
11293
|
+
function selectCompetingPrs(prs, selfLogin) {
|
|
11294
|
+
if (!Array.isArray(prs)) return [];
|
|
11295
|
+
return prs.filter((pr) => pr && pr.author !== selfLogin);
|
|
11296
|
+
}
|
|
11288
11297
|
async function listOpenPRsReferencingIssue(repoFullName, issueNumber) {
|
|
11289
11298
|
try {
|
|
11290
11299
|
const res = await fetch(`${GH_API2}/repos/${repoFullName}/pulls?state=open&per_page=100`, {
|
|
@@ -11350,6 +11359,65 @@ async function fetchIssue(repoFullName, issueNumber) {
|
|
|
11350
11359
|
return null;
|
|
11351
11360
|
}
|
|
11352
11361
|
}
|
|
11362
|
+
function buildAssignmentComment(repoFullName) {
|
|
11363
|
+
const usesTakeBot = TAKE_BOT_REPOS.has(repoFullName.toLowerCase());
|
|
11364
|
+
const body = usesTakeBot ? "/take" : `I'd like to work on this \u2014 could I be assigned? Thanks!
|
|
11365
|
+
|
|
11366
|
+
${ASSIGNMENT_MARKER}`;
|
|
11367
|
+
return { usesTakeBot, body };
|
|
11368
|
+
}
|
|
11369
|
+
async function hasPriorAssignmentRequest(repoFullName, issueNumber, login) {
|
|
11370
|
+
try {
|
|
11371
|
+
const res = await fetch(
|
|
11372
|
+
`${GH_API2}/repos/${repoFullName}/issues/${issueNumber}/comments?per_page=100&sort=created&direction=desc`,
|
|
11373
|
+
{ headers: GH_HEADERS2, signal: AbortSignal.timeout(1e4) }
|
|
11374
|
+
);
|
|
11375
|
+
if (!res.ok) return false;
|
|
11376
|
+
const comments = await res.json();
|
|
11377
|
+
if (!Array.isArray(comments)) return false;
|
|
11378
|
+
return comments.some(
|
|
11379
|
+
(c) => c && c.user && c.user.login === login && typeof c.body === "string" && c.body.includes(ASSIGNMENT_MARKER)
|
|
11380
|
+
);
|
|
11381
|
+
} catch {
|
|
11382
|
+
return false;
|
|
11383
|
+
}
|
|
11384
|
+
}
|
|
11385
|
+
async function requestIssueAssignment(claim, flags = {}, ghUser) {
|
|
11386
|
+
if (flags["no-assign"]) {
|
|
11387
|
+
console.log(" (--no-assign \u2014 skipping the assignment request; request it manually before working)");
|
|
11388
|
+
return;
|
|
11389
|
+
}
|
|
11390
|
+
const parsed = parseGitHubUrl(claim.issueUrl);
|
|
11391
|
+
if (!parsed) return;
|
|
11392
|
+
const { repoFullName, number: number3 } = parsed;
|
|
11393
|
+
let login = ghUser;
|
|
11394
|
+
if (!login) {
|
|
11395
|
+
try {
|
|
11396
|
+
login = await sh("gh", ["api", "user", "-q", ".login"]);
|
|
11397
|
+
} catch {
|
|
11398
|
+
console.log(" (assignment not requested: 'gh' not authenticated \u2014 comment on the issue manually before working)");
|
|
11399
|
+
return;
|
|
11400
|
+
}
|
|
11401
|
+
}
|
|
11402
|
+
const issue2 = await fetchIssue(repoFullName, number3);
|
|
11403
|
+
if (issue2 && issue2.assignees.includes(login)) {
|
|
11404
|
+
console.log(` \u2713 Already assigned to @${login} on ${repoFullName}#${number3}.`);
|
|
11405
|
+
return;
|
|
11406
|
+
}
|
|
11407
|
+
const { usesTakeBot, body } = buildAssignmentComment(repoFullName);
|
|
11408
|
+
if (!usesTakeBot && await hasPriorAssignmentRequest(repoFullName, number3, login)) {
|
|
11409
|
+
console.log(` \u2713 Assignment already requested on ${repoFullName}#${number3}.`);
|
|
11410
|
+
return;
|
|
11411
|
+
}
|
|
11412
|
+
try {
|
|
11413
|
+
await sh("gh", ["issue", "comment", String(number3), "--repo", repoFullName, "--body", body]);
|
|
11414
|
+
console.log(
|
|
11415
|
+
usesTakeBot ? ` \u2713 Requested assignment via /take on ${repoFullName}#${number3}.` : ` \u2713 Requested assignment on ${repoFullName}#${number3}.`
|
|
11416
|
+
);
|
|
11417
|
+
} catch (err) {
|
|
11418
|
+
console.log(` (could not post the assignment request: ${err.stderr || err.message || err} \u2014 do it manually before working)`);
|
|
11419
|
+
}
|
|
11420
|
+
}
|
|
11353
11421
|
async function pollPR(prUrl) {
|
|
11354
11422
|
const p = parseGitHubUrl(prUrl);
|
|
11355
11423
|
if (!p) return null;
|
|
@@ -11565,10 +11633,19 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
|
|
|
11565
11633
|
console.log(" \u2022 MUST NOT `git push` or `gh pr` \u2014 pushing happens only via `terminalhire submit`");
|
|
11566
11634
|
console.log(" \u2022 clone + static analysis + patch only; NO test/build execution without explicit approval");
|
|
11567
11635
|
console.log(" \u2022 no access to ~/.terminalhire (the executor never needs your profile)");
|
|
11568
|
-
console.log("\n Next:");
|
|
11569
|
-
console.log("
|
|
11570
|
-
console.log("
|
|
11571
|
-
console.log("
|
|
11636
|
+
console.log("\n Next \u2014 start work (forks + clones into an isolated worktree; your terminal stays put):");
|
|
11637
|
+
console.log(" terminalhire claim start " + claim.id);
|
|
11638
|
+
console.log(" Then publish when it is done (the only step that pushes + opens the PR):");
|
|
11639
|
+
console.log(" terminalhire claim submit " + claim.id);
|
|
11640
|
+
if (flags.start) {
|
|
11641
|
+
await cmdStart(claim.id, { start: true });
|
|
11642
|
+
} else if (process.stdin.isTTY && !flags["no-start"]) {
|
|
11643
|
+
const go = await confirm(`
|
|
11644
|
+
Fork ${claim.repoFullName} and start now? (y/N) `);
|
|
11645
|
+
if (go) await cmdStart(claim.id, { start: true });
|
|
11646
|
+
else console.log(`
|
|
11647
|
+
Saved. Start anytime: terminalhire claim start ${claim.id}`);
|
|
11648
|
+
}
|
|
11572
11649
|
}
|
|
11573
11650
|
async function cmdPreview(arg, { json } = {}) {
|
|
11574
11651
|
if (!arg) {
|
|
@@ -11765,6 +11842,181 @@ async function cmdAttach(id, worktree, branch) {
|
|
|
11765
11842
|
claims.updateClaim(id, { worktreePath: toplevel, branch });
|
|
11766
11843
|
console.log(`Attached ${id}: worktree=${toplevel} branch=${branch}`);
|
|
11767
11844
|
}
|
|
11845
|
+
function workDirFor(repoFullName, issueNumber) {
|
|
11846
|
+
const [owner, repo] = String(repoFullName).split("/");
|
|
11847
|
+
const suffix = issueNumber ? `-${issueNumber}` : "";
|
|
11848
|
+
return join17(homedir15(), "terminalhire", "work", `${owner}-${repo}${suffix}`);
|
|
11849
|
+
}
|
|
11850
|
+
function startBranchFor(repoFullName, issueNumber) {
|
|
11851
|
+
const repo = String(repoFullName).split("/")[1] || "claim";
|
|
11852
|
+
return `th/${repo}-${issueNumber || "work"}`;
|
|
11853
|
+
}
|
|
11854
|
+
function explicitForkConsent(flags = {}) {
|
|
11855
|
+
return Boolean(flags.start) || Boolean(flags.fork);
|
|
11856
|
+
}
|
|
11857
|
+
async function ensureForkExists(repoFullName, ghUser) {
|
|
11858
|
+
const repoShort = repoFullName.split("/")[1];
|
|
11859
|
+
const forkFullName = `${ghUser}/${repoShort}`;
|
|
11860
|
+
await sh("gh", ["repo", "fork", repoFullName, "--clone=false"]);
|
|
11861
|
+
let isFork = false;
|
|
11862
|
+
try {
|
|
11863
|
+
isFork = await sh("gh", ["api", `repos/${forkFullName}`, "--jq", ".fork"]) === "true";
|
|
11864
|
+
} catch {
|
|
11865
|
+
isFork = false;
|
|
11866
|
+
}
|
|
11867
|
+
if (!isFork) throw new Error(`fork ${forkFullName} created but could not be verified as a fork`);
|
|
11868
|
+
return forkFullName;
|
|
11869
|
+
}
|
|
11870
|
+
async function cmdStart(id, flags = {}) {
|
|
11871
|
+
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
11872
|
+
if (!id) {
|
|
11873
|
+
const startable = claims.listClaims({ active: true }).filter((c) => c.state === "claimed");
|
|
11874
|
+
if (startable.length === 0) {
|
|
11875
|
+
console.log("No claims ready to start. Claim one first: terminalhire claim <ref>");
|
|
11876
|
+
return;
|
|
11877
|
+
}
|
|
11878
|
+
console.log(`
|
|
11879
|
+
${startable.length} claim${startable.length === 1 ? "" : "s"} ready to start:
|
|
11880
|
+
`);
|
|
11881
|
+
for (const c of startable) {
|
|
11882
|
+
console.log(` ${c.title}`);
|
|
11883
|
+
console.log(` terminalhire claim start ${c.id}`);
|
|
11884
|
+
}
|
|
11885
|
+
console.log("\nRun the command under the one you want to start.");
|
|
11886
|
+
return;
|
|
11887
|
+
}
|
|
11888
|
+
const claim = claims.findClaim(id);
|
|
11889
|
+
if (!claim) {
|
|
11890
|
+
console.error(`terminalhire claim: no claim with id '${id}'.`);
|
|
11891
|
+
process.exit(1);
|
|
11892
|
+
}
|
|
11893
|
+
if (claim.worktreePath) {
|
|
11894
|
+
let stillThere = false;
|
|
11895
|
+
try {
|
|
11896
|
+
stillThere = await sh("git", ["-C", claim.worktreePath, "rev-parse", "--show-toplevel"]) === claim.worktreePath;
|
|
11897
|
+
} catch {
|
|
11898
|
+
stillThere = false;
|
|
11899
|
+
}
|
|
11900
|
+
if (stillThere) {
|
|
11901
|
+
console.log("Already started \u2014 your worktree is ready (your terminal stays put):");
|
|
11902
|
+
console.log(` cd ${claim.worktreePath}`);
|
|
11903
|
+
if (claim.branch) console.log(` branch: ${claim.branch}`);
|
|
11904
|
+
console.log(`
|
|
11905
|
+
When it's done: terminalhire claim submit ${id}`);
|
|
11906
|
+
return;
|
|
11907
|
+
}
|
|
11908
|
+
}
|
|
11909
|
+
if (flags.here) {
|
|
11910
|
+
await cmdStartHere(claims, claim, flags);
|
|
11911
|
+
return;
|
|
11912
|
+
}
|
|
11913
|
+
let ghUser;
|
|
11914
|
+
try {
|
|
11915
|
+
ghUser = await sh("gh", ["api", "user", "-q", ".login"]);
|
|
11916
|
+
} catch {
|
|
11917
|
+
console.error("terminalhire claim: 'gh' CLI not available or not authenticated. Run 'gh auth login'.");
|
|
11918
|
+
process.exit(1);
|
|
11919
|
+
}
|
|
11920
|
+
let consented = explicitForkConsent(flags);
|
|
11921
|
+
if (!consented && process.stdin.isTTY) {
|
|
11922
|
+
consented = await confirm(`
|
|
11923
|
+
Fork ${claim.repoFullName} to @${ghUser} and clone it to start? (y/N) `);
|
|
11924
|
+
}
|
|
11925
|
+
if (!consented) {
|
|
11926
|
+
console.error(
|
|
11927
|
+
`
|
|
11928
|
+
terminalhire claim: not started \u2014 starting forks ${claim.repoFullName} to your GitHub account (a network write).
|
|
11929
|
+
Re-run in a terminal to confirm interactively, or pass --fork to consent non-interactively.`
|
|
11930
|
+
);
|
|
11931
|
+
process.exit(1);
|
|
11932
|
+
}
|
|
11933
|
+
const issueNumber = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
11934
|
+
const destDir = workDirFor(claim.repoFullName, issueNumber);
|
|
11935
|
+
if (existsSync10(destDir)) {
|
|
11936
|
+
console.error(
|
|
11937
|
+
`terminalhire claim: ${destDir} already exists \u2014 refusing to clobber it.
|
|
11938
|
+
Remove it and retry, or attach it: terminalhire claim attach ${id} --worktree ${destDir} --branch <branch>`
|
|
11939
|
+
);
|
|
11940
|
+
process.exit(1);
|
|
11941
|
+
}
|
|
11942
|
+
mkdirSync11(join17(homedir15(), "terminalhire", "work"), { recursive: true });
|
|
11943
|
+
let forkFullName;
|
|
11944
|
+
try {
|
|
11945
|
+
forkFullName = await ensureForkExists(claim.repoFullName, ghUser);
|
|
11946
|
+
} catch (err) {
|
|
11947
|
+
console.error(`terminalhire claim: could not create your fork of ${claim.repoFullName}. ${err.message ?? err}`);
|
|
11948
|
+
process.exit(1);
|
|
11949
|
+
}
|
|
11950
|
+
try {
|
|
11951
|
+
await sh("gh", ["repo", "clone", forkFullName, destDir]);
|
|
11952
|
+
} catch (err) {
|
|
11953
|
+
try {
|
|
11954
|
+
rmSync4(destDir, { recursive: true, force: true });
|
|
11955
|
+
} catch {
|
|
11956
|
+
}
|
|
11957
|
+
console.error(`terminalhire claim: clone of ${forkFullName} failed. ${err.stderr || err.message || err}`);
|
|
11958
|
+
process.exit(1);
|
|
11959
|
+
}
|
|
11960
|
+
try {
|
|
11961
|
+
await sh("git", ["-C", destDir, "remote", "get-url", "upstream"]);
|
|
11962
|
+
} catch {
|
|
11963
|
+
try {
|
|
11964
|
+
await sh("git", ["-C", destDir, "remote", "add", "upstream", `https://github.com/${claim.repoFullName}.git`]);
|
|
11965
|
+
} catch {
|
|
11966
|
+
}
|
|
11967
|
+
}
|
|
11968
|
+
const branch = startBranchFor(claim.repoFullName, issueNumber);
|
|
11969
|
+
await sh("git", ["-C", destDir, "checkout", "-b", branch]);
|
|
11970
|
+
const toplevel = await sh("git", ["-C", destDir, "rev-parse", "--show-toplevel"]);
|
|
11971
|
+
claims.updateClaim(id, { worktreePath: toplevel, branch, state: "working" });
|
|
11972
|
+
await requestIssueAssignment(claim, flags, ghUser);
|
|
11973
|
+
console.log(`
|
|
11974
|
+
\u2713 Started: ${claim.title}`);
|
|
11975
|
+
console.log(` fork: ${forkFullName}`);
|
|
11976
|
+
console.log(` branch: ${branch}`);
|
|
11977
|
+
console.log("\n Your isolated worktree is ready \u2014 your current terminal is untouched:");
|
|
11978
|
+
console.log(` cd ${toplevel}`);
|
|
11979
|
+
console.log("\n When the work is done (the only step that pushes + opens the PR):");
|
|
11980
|
+
console.log(` terminalhire claim submit ${id}`);
|
|
11981
|
+
}
|
|
11982
|
+
async function cmdStartHere(claims, claim, flags = {}) {
|
|
11983
|
+
let toplevel;
|
|
11984
|
+
try {
|
|
11985
|
+
toplevel = await sh("git", ["-C", process.cwd(), "rev-parse", "--show-toplevel"]);
|
|
11986
|
+
} catch {
|
|
11987
|
+
console.error("terminalhire claim: --here must be run inside a git repository.");
|
|
11988
|
+
process.exit(1);
|
|
11989
|
+
}
|
|
11990
|
+
const remotesOut = await sh("git", ["-C", toplevel, "remote", "-v"]).catch(() => "");
|
|
11991
|
+
const repos = new Set(
|
|
11992
|
+
remotesOut.split("\n").map((l) => parseRepoFromRemote(l.split(/\s+/)[1])).filter(Boolean).map((r) => r.toLowerCase())
|
|
11993
|
+
);
|
|
11994
|
+
if (!repos.has(claim.repoFullName.toLowerCase())) {
|
|
11995
|
+
console.error(
|
|
11996
|
+
`terminalhire claim: --here expects a clone of ${claim.repoFullName}, but no remote here points there.
|
|
11997
|
+
Use \`terminalhire claim start ${claim.id}\` (no --here) to fork + clone it into a fresh worktree.`
|
|
11998
|
+
);
|
|
11999
|
+
process.exit(1);
|
|
12000
|
+
}
|
|
12001
|
+
const dirty = await sh("git", ["-C", toplevel, "status", "--porcelain", "--untracked-files=no"]).catch(() => "");
|
|
12002
|
+
if (dirty) {
|
|
12003
|
+
console.error(
|
|
12004
|
+
"terminalhire claim: --here needs a clean working tree (claim work must not mix with your current changes).\n Commit or stash first, or use `terminalhire claim start` for an isolated worktree."
|
|
12005
|
+
);
|
|
12006
|
+
process.exit(1);
|
|
12007
|
+
}
|
|
12008
|
+
const issueNumber = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
12009
|
+
const branch = startBranchFor(claim.repoFullName, issueNumber);
|
|
12010
|
+
await sh("git", ["-C", toplevel, "checkout", "-b", branch]);
|
|
12011
|
+
claims.updateClaim(claim.id, { worktreePath: toplevel, branch, state: "working" });
|
|
12012
|
+
await requestIssueAssignment(claim, flags);
|
|
12013
|
+
console.log(`
|
|
12014
|
+
\u2713 Started here: ${claim.title}`);
|
|
12015
|
+
console.log(` worktree: ${toplevel}`);
|
|
12016
|
+
console.log(` branch: ${branch}`);
|
|
12017
|
+
console.log(`
|
|
12018
|
+
When the work is done: terminalhire claim submit ${claim.id}`);
|
|
12019
|
+
}
|
|
11768
12020
|
async function cmdSubmit(id, flags = {}) {
|
|
11769
12021
|
const worktreeOverride = flags.worktree;
|
|
11770
12022
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
@@ -11779,10 +12031,10 @@ async function cmdSubmit(id, flags = {}) {
|
|
|
11779
12031
|
console.error(`terminalhire claim: no claim with id '${id}'.`);
|
|
11780
12032
|
process.exit(1);
|
|
11781
12033
|
}
|
|
11782
|
-
if (claim.state !== "ready") {
|
|
12034
|
+
if (claim.state !== "working" && claim.state !== "ready") {
|
|
11783
12035
|
console.error(
|
|
11784
|
-
`terminalhire claim: ${id} is '${claim.state}'
|
|
11785
|
-
terminalhire claim
|
|
12036
|
+
`terminalhire claim: ${id} is '${claim.state}'. Submit runs once work has started ('working', via 'claim start') or the review gate cleared it ('ready'). Start it first:
|
|
12037
|
+
terminalhire claim start ${id}`
|
|
11786
12038
|
);
|
|
11787
12039
|
process.exit(1);
|
|
11788
12040
|
}
|
|
@@ -11989,10 +12241,22 @@ async function cmdSubmit(id, flags = {}) {
|
|
|
11989
12241
|
const issueNo = (parseGitHubUrl(claim.issueUrl) || {}).number;
|
|
11990
12242
|
const contention = issueNo ? await listOpenPRsReferencingIssue(claim.repoFullName, issueNo) : null;
|
|
11991
12243
|
if (contention && contention.total > 0) {
|
|
11992
|
-
console.log(` \u26A0 contention: ${contention.total} open PR(s) already reference this issue:`);
|
|
11993
12244
|
const nowMs = Date.now();
|
|
12245
|
+
console.log(` \u26A0 contention: ${contention.total} open PR(s) already reference this issue:`);
|
|
11994
12246
|
for (const pr of contention.prs) console.log(fmtContentionPr(pr, nowMs, false));
|
|
11995
12247
|
console.log(CONTENTION_HINT);
|
|
12248
|
+
const competing = selectCompetingPrs(contention.prs, ghUser);
|
|
12249
|
+
if (competing.length > 0 && !flags["force-competing"]) {
|
|
12250
|
+
console.error(
|
|
12251
|
+
`
|
|
12252
|
+
terminalhire claim: refusing to submit \u2014 ${competing.length} open PR(s) by someone else already address ${claim.repoFullName}#${issueNo}:`
|
|
12253
|
+
);
|
|
12254
|
+
for (const pr of competing) console.error(fmtContentionPr(pr, nowMs, false));
|
|
12255
|
+
console.error(
|
|
12256
|
+
"\n Opening a competing PR reads as low-effort duplication and burns maintainer goodwill.\n Stand down, or add value on the existing PR instead (a review, a test, a comment).\n If you are certain yours should still go up, re-run with --force-competing."
|
|
12257
|
+
);
|
|
12258
|
+
process.exit(1);
|
|
12259
|
+
}
|
|
11996
12260
|
}
|
|
11997
12261
|
}
|
|
11998
12262
|
let ok;
|
|
@@ -12374,6 +12638,9 @@ async function run7() {
|
|
|
12374
12638
|
case "record":
|
|
12375
12639
|
await cmdRecord(positional[0], flags);
|
|
12376
12640
|
break;
|
|
12641
|
+
case "start":
|
|
12642
|
+
await cmdStart(positional[0], flags);
|
|
12643
|
+
break;
|
|
12377
12644
|
case "list":
|
|
12378
12645
|
await cmdList(active);
|
|
12379
12646
|
break;
|
|
@@ -12393,7 +12660,7 @@ async function run7() {
|
|
|
12393
12660
|
await cmdRelease(positional[0]);
|
|
12394
12661
|
break;
|
|
12395
12662
|
default:
|
|
12396
|
-
console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | attach | list | status | update | submit | release`);
|
|
12663
|
+
console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | start | attach | list | status | update | submit | release`);
|
|
12397
12664
|
process.exit(1);
|
|
12398
12665
|
}
|
|
12399
12666
|
} catch (err) {
|
|
@@ -12401,7 +12668,7 @@ async function run7() {
|
|
|
12401
12668
|
process.exit(1);
|
|
12402
12669
|
}
|
|
12403
12670
|
}
|
|
12404
|
-
var TERMINALHIRE_DIR13, INDEX_CACHE_FILE5, CLAIM_PUSH_MARKER, API_URL6, CLAIM_SYNC_BASE2, CLAIM_CONSENT_VERSION, CLAIM_POLL_INTERVAL_MS, CLAIM_POLL_TIMEOUT_MS, GH_API2, GH_HEADERS2, CONTENTION_HINT, AI_DISCLOSURE_NOTE, pExecFile, VALUE_FLAGS;
|
|
12671
|
+
var TERMINALHIRE_DIR13, INDEX_CACHE_FILE5, CLAIM_PUSH_MARKER, API_URL6, CLAIM_SYNC_BASE2, CLAIM_CONSENT_VERSION, CLAIM_POLL_INTERVAL_MS, CLAIM_POLL_TIMEOUT_MS, GH_API2, GH_HEADERS2, CONTENTION_HINT, AI_DISCLOSURE_NOTE, pExecFile, VALUE_FLAGS, ASSIGNMENT_MARKER, TAKE_BOT_REPOS;
|
|
12405
12672
|
var init_jpi_claim = __esm({
|
|
12406
12673
|
"bin/jpi-claim.js"() {
|
|
12407
12674
|
"use strict";
|
|
@@ -12423,6 +12690,8 @@ var init_jpi_claim = __esm({
|
|
|
12423
12690
|
AI_DISCLOSURE_NOTE = "---\nThis contribution was developed with AI assistance via [terminalhire](https://terminalhire.com). The author has reviewed the change and takes responsibility for its content.";
|
|
12424
12691
|
pExecFile = promisify(execFile);
|
|
12425
12692
|
VALUE_FLAGS = /* @__PURE__ */ new Set(["worktree", "branch", "body-file", "title"]);
|
|
12693
|
+
ASSIGNMENT_MARKER = "<!-- terminalhire:assignment-request -->";
|
|
12694
|
+
TAKE_BOT_REPOS = /* @__PURE__ */ new Set(["paradedb/paradedb"]);
|
|
12426
12695
|
}
|
|
12427
12696
|
});
|
|
12428
12697
|
|
|
@@ -42869,6 +43138,7 @@ if (!firstArg || firstArg === "help" || firstArg === "--help" || firstArg === "-
|
|
|
42869
43138
|
console.log(" terminalhire bounties --priced Only bounties with a known $ amount");
|
|
42870
43139
|
console.log(" terminalhire contribute Open issues where a merged PR counts toward your r\xE9sum\xE9");
|
|
42871
43140
|
console.log(" terminalhire claim record <id|issueUrl> Claim a bounty locally + print the executor brief");
|
|
43141
|
+
console.log(" terminalhire claim start [<id>] Fork + clone + branch into an isolated worktree (never cd's you)");
|
|
42872
43142
|
console.log(" terminalhire claim list [--active] List your claims + accepted-PR rate");
|
|
42873
43143
|
console.log(" terminalhire claim status [<id>] Poll source PR merge state (updates the metric)");
|
|
42874
43144
|
console.log(" terminalhire claim --push Opt-in: show your claims on your dashboard (typed-yes + browser confirm)");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminalhire",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Local-first job matching for developers — ambient job matches in the Claude Code spinner. Matching runs on your machine; your profile never leaves it.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|