terminalhire 0.30.2 → 0.33.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/claim-push-bg.js +2 -2
- package/dist/bin/jpi-bounties.js +147 -13
- package/dist/bin/jpi-chat-read.js +2 -2
- package/dist/bin/jpi-chat.js +2 -2
- package/dist/bin/jpi-claim.js +182 -34
- package/dist/bin/jpi-contribute.js +144 -13
- package/dist/bin/jpi-devs.js +30 -4
- package/dist/bin/jpi-dispatch.js +437 -240
- package/dist/bin/jpi-hub.js +68 -37
- package/dist/bin/jpi-inbox.js +2 -2
- package/dist/bin/jpi-init.js +1 -1
- package/dist/bin/jpi-intro.js +2 -2
- package/dist/bin/jpi-jobs.js +3 -3
- package/dist/bin/jpi-learn.js +2 -2
- package/dist/bin/jpi-login.js +3 -3
- package/dist/bin/jpi-profile.js +2 -2
- package/dist/bin/jpi-project.js +1 -1
- package/dist/bin/jpi-refresh.js +12 -7
- package/dist/bin/jpi-save.js +2 -2
- package/dist/bin/jpi-sync.js +2 -2
- package/dist/bin/jpi-trajectory.js +2 -2
- package/dist/src/chat-client.js +2 -2
- package/dist/src/chat-keystore.js +2 -2
- package/dist/src/github-auth.js +2 -2
- package/dist/src/intro.js +2 -2
- package/dist/src/profile.js +2 -2
- package/dist/src/repo-policy.js +87 -16
- package/dist/src/trajectory.js +2 -2
- package/package.json +1 -1
|
@@ -45,9 +45,9 @@ var init_keytar = __esm({
|
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
48
|
+
// node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
|
|
49
49
|
var require_keytar = __commonJS({
|
|
50
|
-
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
50
|
+
"node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
51
51
|
"use strict";
|
|
52
52
|
init_keytar();
|
|
53
53
|
try {
|
package/dist/bin/jpi-bounties.js
CHANGED
|
@@ -2026,7 +2026,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
2026
2026
|
reason: buildReason(details)
|
|
2027
2027
|
};
|
|
2028
2028
|
});
|
|
2029
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2029
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2030
2030
|
const byScore = b.score - a.score;
|
|
2031
2031
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2032
2032
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
|
@@ -8062,9 +8062,9 @@ var init_keytar = __esm({
|
|
|
8062
8062
|
}
|
|
8063
8063
|
});
|
|
8064
8064
|
|
|
8065
|
-
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
8065
|
+
// node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
|
|
8066
8066
|
var require_keytar = __commonJS({
|
|
8067
|
-
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
8067
|
+
"node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
8068
8068
|
"use strict";
|
|
8069
8069
|
init_keytar();
|
|
8070
8070
|
try {
|
|
@@ -8353,11 +8353,134 @@ var init_profile = __esm({
|
|
|
8353
8353
|
}
|
|
8354
8354
|
});
|
|
8355
8355
|
|
|
8356
|
-
//
|
|
8357
|
-
|
|
8358
|
-
|
|
8356
|
+
// src/claims.ts
|
|
8357
|
+
var claims_exports = {};
|
|
8358
|
+
__export(claims_exports, {
|
|
8359
|
+
PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
|
|
8360
|
+
acceptedPRRate: () => acceptedPRRate,
|
|
8361
|
+
findClaim: () => findClaim,
|
|
8362
|
+
listClaims: () => listClaims,
|
|
8363
|
+
readClaims: () => readClaims,
|
|
8364
|
+
recordClaim: () => recordClaim,
|
|
8365
|
+
removeClaim: () => removeClaim,
|
|
8366
|
+
toPushedClaim: () => toPushedClaim,
|
|
8367
|
+
updateClaim: () => updateClaim
|
|
8368
|
+
});
|
|
8369
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, renameSync as renameSync2, existsSync as existsSync2 } from "fs";
|
|
8359
8370
|
import { join as join4 } from "path";
|
|
8360
8371
|
import { homedir as homedir3 } from "os";
|
|
8372
|
+
function toPushedClaim(claim) {
|
|
8373
|
+
return {
|
|
8374
|
+
kind: claim.kind,
|
|
8375
|
+
repoFullName: claim.repoFullName,
|
|
8376
|
+
state: claim.state,
|
|
8377
|
+
prUrl: claim.prUrl,
|
|
8378
|
+
merged: claim.state === "merged",
|
|
8379
|
+
claimedAt: claim.claimedAt,
|
|
8380
|
+
updatedAt: claim.updatedAt
|
|
8381
|
+
};
|
|
8382
|
+
}
|
|
8383
|
+
function nowISO() {
|
|
8384
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
8385
|
+
}
|
|
8386
|
+
function normalizeClaim(c) {
|
|
8387
|
+
return { ...c, kind: c.kind ?? "bounty", policy: c.policy ?? null };
|
|
8388
|
+
}
|
|
8389
|
+
function readClaims() {
|
|
8390
|
+
try {
|
|
8391
|
+
if (!existsSync2(CLAIMS_FILE)) return [];
|
|
8392
|
+
const data = JSON.parse(readFileSync4(CLAIMS_FILE, "utf8"));
|
|
8393
|
+
const claims = Array.isArray(data?.claims) ? data.claims : [];
|
|
8394
|
+
return claims.map(normalizeClaim);
|
|
8395
|
+
} catch {
|
|
8396
|
+
return [];
|
|
8397
|
+
}
|
|
8398
|
+
}
|
|
8399
|
+
function writeClaims(claims) {
|
|
8400
|
+
mkdirSync3(TERMINALHIRE_DIR3, { recursive: true });
|
|
8401
|
+
const tmp = `${CLAIMS_FILE}.tmp`;
|
|
8402
|
+
const payload = { claims };
|
|
8403
|
+
writeFileSync3(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
8404
|
+
renameSync2(tmp, CLAIMS_FILE);
|
|
8405
|
+
}
|
|
8406
|
+
function findClaim(id) {
|
|
8407
|
+
return readClaims().find((c) => c.id === id) ?? null;
|
|
8408
|
+
}
|
|
8409
|
+
function listClaims(opts = {}) {
|
|
8410
|
+
const claims = readClaims();
|
|
8411
|
+
if (!opts.active) return claims;
|
|
8412
|
+
return claims.filter((c) => !TERMINAL_STATES.has(c.state));
|
|
8413
|
+
}
|
|
8414
|
+
function recordClaim(rec) {
|
|
8415
|
+
const claims = readClaims();
|
|
8416
|
+
if (claims.some((c) => c.id === rec.id)) {
|
|
8417
|
+
throw new Error(
|
|
8418
|
+
`claim already exists for '${rec.id}' \u2014 run 'terminalhire claim status ${rec.id}' or 'terminalhire claim release ${rec.id}'`
|
|
8419
|
+
);
|
|
8420
|
+
}
|
|
8421
|
+
const ts = nowISO();
|
|
8422
|
+
const claim = {
|
|
8423
|
+
...rec,
|
|
8424
|
+
// Defensive default (mirrors normalizeClaim's `kind ?? 'bounty'` pattern):
|
|
8425
|
+
// a caller written before `policy` existed, or a plain-JS caller that skips
|
|
8426
|
+
// it, still produces a valid record instead of `policy: undefined`.
|
|
8427
|
+
policy: rec.policy ?? null,
|
|
8428
|
+
state: "claimed",
|
|
8429
|
+
worktreePath: null,
|
|
8430
|
+
branch: null,
|
|
8431
|
+
prUrl: null,
|
|
8432
|
+
review: null,
|
|
8433
|
+
claimedAt: ts,
|
|
8434
|
+
updatedAt: ts
|
|
8435
|
+
};
|
|
8436
|
+
claims.push(claim);
|
|
8437
|
+
writeClaims(claims);
|
|
8438
|
+
return claim;
|
|
8439
|
+
}
|
|
8440
|
+
function updateClaim(id, patch) {
|
|
8441
|
+
const claims = readClaims();
|
|
8442
|
+
const idx = claims.findIndex((c) => c.id === id);
|
|
8443
|
+
if (idx === -1) return null;
|
|
8444
|
+
claims[idx] = { ...claims[idx], ...patch, updatedAt: nowISO() };
|
|
8445
|
+
writeClaims(claims);
|
|
8446
|
+
return claims[idx];
|
|
8447
|
+
}
|
|
8448
|
+
function removeClaim(id) {
|
|
8449
|
+
const claims = readClaims();
|
|
8450
|
+
const next = claims.filter((c) => c.id !== id);
|
|
8451
|
+
if (next.length === claims.length) return false;
|
|
8452
|
+
writeClaims(next);
|
|
8453
|
+
return true;
|
|
8454
|
+
}
|
|
8455
|
+
function acceptedPRRate(claims = readClaims()) {
|
|
8456
|
+
const total = claims.length;
|
|
8457
|
+
const merged = claims.filter((c) => c.state === "merged").length;
|
|
8458
|
+
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
8459
|
+
}
|
|
8460
|
+
var TERMINALHIRE_DIR3, CLAIMS_FILE, PUSHED_CLAIM_FIELDS, TERMINAL_STATES;
|
|
8461
|
+
var init_claims = __esm({
|
|
8462
|
+
"src/claims.ts"() {
|
|
8463
|
+
"use strict";
|
|
8464
|
+
TERMINALHIRE_DIR3 = process.env.TERMINALHIRE_DIR || join4(homedir3(), ".terminalhire");
|
|
8465
|
+
CLAIMS_FILE = join4(TERMINALHIRE_DIR3, "claims.json");
|
|
8466
|
+
PUSHED_CLAIM_FIELDS = [
|
|
8467
|
+
"kind",
|
|
8468
|
+
"repoFullName",
|
|
8469
|
+
"state",
|
|
8470
|
+
"prUrl",
|
|
8471
|
+
"merged",
|
|
8472
|
+
"claimedAt",
|
|
8473
|
+
"updatedAt"
|
|
8474
|
+
];
|
|
8475
|
+
TERMINAL_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
8476
|
+
}
|
|
8477
|
+
});
|
|
8478
|
+
|
|
8479
|
+
// bin/jpi-bounties.js
|
|
8480
|
+
init_src();
|
|
8481
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
8482
|
+
import { join as join5 } from "path";
|
|
8483
|
+
import { homedir as homedir4 } from "os";
|
|
8361
8484
|
import { createInterface } from "readline";
|
|
8362
8485
|
|
|
8363
8486
|
// bin/cache-store.js
|
|
@@ -8422,8 +8545,8 @@ function linkTitle(title, url) {
|
|
|
8422
8545
|
}
|
|
8423
8546
|
|
|
8424
8547
|
// bin/jpi-bounties.js
|
|
8425
|
-
var
|
|
8426
|
-
var INDEX_CACHE_FILE2 =
|
|
8548
|
+
var TERMINALHIRE_DIR4 = process.env.TERMINALHIRE_DIR || join5(homedir4(), ".terminalhire");
|
|
8549
|
+
var INDEX_CACHE_FILE2 = join5(TERMINALHIRE_DIR4, "index-cache.json");
|
|
8427
8550
|
var INDEX_TTL_MS = 15 * 60 * 1e3;
|
|
8428
8551
|
var API_URL = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
8429
8552
|
var RANK_MODE = process.env["TERMINALHIRE_BOUNTY_RANK"] ?? "winnability";
|
|
@@ -8436,7 +8559,7 @@ var SHOW_ALL = args.includes("--all");
|
|
|
8436
8559
|
var WINNABLE_ONLY = args.includes("--winnable");
|
|
8437
8560
|
function readIndexCache() {
|
|
8438
8561
|
try {
|
|
8439
|
-
const entry = JSON.parse(
|
|
8562
|
+
const entry = JSON.parse(readFileSync5(INDEX_CACHE_FILE2, "utf8"));
|
|
8440
8563
|
if (Date.now() - entry.ts < INDEX_TTL_MS) return entry.index;
|
|
8441
8564
|
return null;
|
|
8442
8565
|
} catch {
|
|
@@ -8468,22 +8591,26 @@ function formatAmount(b) {
|
|
|
8468
8591
|
return b.amountUSD != null ? "$" + b.amountUSD.toLocaleString() : "$\u2014";
|
|
8469
8592
|
}
|
|
8470
8593
|
var EFFORT_LABEL = { small: "small (~\xBD day)", medium: "medium (~1 day)", large: "large (multi-day)" };
|
|
8471
|
-
function printBounty(i, job, score, reason, matchedTags) {
|
|
8594
|
+
function printBounty(i, job, score, reason, matchedTags, claimedIds = /* @__PURE__ */ new Set()) {
|
|
8472
8595
|
const b = job.bounty ?? {};
|
|
8473
8596
|
const stars = b.repoStars != null ? ` \xB7 ${b.repoStars}\u2605` : "";
|
|
8474
8597
|
const effort = b.estimatedEffort ? ` \xB7 ${EFFORT_LABEL[b.estimatedEffort]}` : "";
|
|
8475
8598
|
const scoreStr = score > 0 ? ` \xB7 match ${Math.round(score * 100)}%` : "";
|
|
8476
8599
|
const prs = b.competingOpenPRs;
|
|
8477
8600
|
const contend = prs != null && prs > 0 ? ` \xB7 \u26A0 ${prs} PR${prs === 1 ? "" : "s"} in flight` : "";
|
|
8601
|
+
const claimed = claimedIds.has(job.id);
|
|
8602
|
+
const badge = claimed ? " \xB7 \u25CF claimed by you" : "";
|
|
8478
8603
|
const ref = opportunityShortToken(job.id);
|
|
8479
8604
|
console.log(`
|
|
8480
8605
|
${i + 1}. ${linkTitle(job.title, job.url)} [${ref}]`);
|
|
8481
|
-
console.log(` ${formatAmount(b)}${effort} \xB7 ${sanitizeText(b.repoFullName ?? job.company)}${stars}${scoreStr}${contend}`);
|
|
8606
|
+
console.log(` ${formatAmount(b)}${effort} \xB7 ${sanitizeText(b.repoFullName ?? job.company)}${stars}${scoreStr}${contend}${badge}`);
|
|
8482
8607
|
if (reason) console.log(` ${reason}`);
|
|
8483
8608
|
if (matchedTags && matchedTags.length) console.log(` Tags matched: ${matchedTags.slice(0, 5).join(", ")}`);
|
|
8484
8609
|
console.log(` id: ${job.id}`);
|
|
8485
8610
|
console.log(` Claim: ${sanitizeText(b.claimUrl ?? job.url)}`);
|
|
8486
|
-
console.log(
|
|
8611
|
+
console.log(
|
|
8612
|
+
claimed ? ` \u2192 claimed by you \u2014 terminalhire claim status ${job.id}` : ` \u2192 terminalhire claim ${ref}`
|
|
8613
|
+
);
|
|
8487
8614
|
}
|
|
8488
8615
|
function rankBounties(bounties, { rankMode = "winnability", scoreOf = () => 0 } = {}) {
|
|
8489
8616
|
const legacy = rankMode === "legacy";
|
|
@@ -8561,6 +8688,12 @@ async function run() {
|
|
|
8561
8688
|
if (result.status !== "ok") return;
|
|
8562
8689
|
const { bounties, ranked, matchedCount } = result;
|
|
8563
8690
|
const shown = SHOW_ALL ? bounties : bounties.slice(0, LIMIT);
|
|
8691
|
+
let claimedIds = /* @__PURE__ */ new Set();
|
|
8692
|
+
try {
|
|
8693
|
+
const { listClaims: listClaims2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
8694
|
+
claimedIds = new Set(listClaims2({ active: true }).map((c) => c.id));
|
|
8695
|
+
} catch {
|
|
8696
|
+
}
|
|
8564
8697
|
console.log(
|
|
8565
8698
|
`
|
|
8566
8699
|
\u26A1 ${bounties.length} bount${bounties.length === 1 ? "y" : "ies"} you could knock out` + (matchedCount ? ` \u2014 ${matchedCount} matched to your profile` : "") + ` (local rank \u2014 no data sent)
|
|
@@ -8568,7 +8701,7 @@ async function run() {
|
|
|
8568
8701
|
);
|
|
8569
8702
|
for (let i = 0; i < shown.length; i++) {
|
|
8570
8703
|
const r = ranked.get(shown[i].id);
|
|
8571
|
-
printBounty(i, shown[i], r?.score ?? 0, r?.reason, r?.matchedTags);
|
|
8704
|
+
printBounty(i, shown[i], r?.score ?? 0, r?.reason, r?.matchedTags, claimedIds);
|
|
8572
8705
|
}
|
|
8573
8706
|
if (!SHOW_ALL && bounties.length > shown.length) {
|
|
8574
8707
|
console.log(`
|
|
@@ -8595,6 +8728,7 @@ export {
|
|
|
8595
8728
|
classifyEmptyStatus,
|
|
8596
8729
|
filterPaidVisibility,
|
|
8597
8730
|
getBounties,
|
|
8731
|
+
printBounty,
|
|
8598
8732
|
rankBounties,
|
|
8599
8733
|
run
|
|
8600
8734
|
};
|
|
@@ -4043,9 +4043,9 @@ var init_keytar = __esm({
|
|
|
4043
4043
|
}
|
|
4044
4044
|
});
|
|
4045
4045
|
|
|
4046
|
-
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
4046
|
+
// node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
|
|
4047
4047
|
var require_keytar = __commonJS({
|
|
4048
|
-
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
4048
|
+
"node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
4049
4049
|
"use strict";
|
|
4050
4050
|
init_keytar();
|
|
4051
4051
|
try {
|
package/dist/bin/jpi-chat.js
CHANGED
|
@@ -4071,9 +4071,9 @@ var init_keytar = __esm({
|
|
|
4071
4071
|
}
|
|
4072
4072
|
});
|
|
4073
4073
|
|
|
4074
|
-
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
4074
|
+
// node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
|
|
4075
4075
|
var require_keytar = __commonJS({
|
|
4076
|
-
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
4076
|
+
"node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
4077
4077
|
"use strict";
|
|
4078
4078
|
init_keytar();
|
|
4079
4079
|
try {
|
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -2026,7 +2026,7 @@ function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
|
|
|
2026
2026
|
reason: buildReason(details)
|
|
2027
2027
|
};
|
|
2028
2028
|
});
|
|
2029
|
-
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => {
|
|
2029
|
+
return scored.filter((r) => r !== null && r.score >= (opts.minScore ?? MIN_SCORE)).sort((a, b) => {
|
|
2030
2030
|
const byScore = b.score - a.score;
|
|
2031
2031
|
if (Math.abs(byScore) > TIEBREAK_EPS) return byScore;
|
|
2032
2032
|
const byAcceptance = (b.acceptance?.count ?? 0) - (a.acceptance?.count ?? 0);
|
|
@@ -8185,9 +8185,9 @@ var init_keytar = __esm({
|
|
|
8185
8185
|
}
|
|
8186
8186
|
});
|
|
8187
8187
|
|
|
8188
|
-
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
8188
|
+
// node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node
|
|
8189
8189
|
var require_keytar = __commonJS({
|
|
8190
|
-
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
8190
|
+
"node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a0bc554500876cd51/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
8191
8191
|
"use strict";
|
|
8192
8192
|
init_keytar();
|
|
8193
8193
|
try {
|
|
@@ -8239,6 +8239,8 @@ var require_keytar2 = __commonJS({
|
|
|
8239
8239
|
// src/repo-policy.ts
|
|
8240
8240
|
var repo_policy_exports = {};
|
|
8241
8241
|
__export(repo_policy_exports, {
|
|
8242
|
+
POLICY_RULESET_VERSION: () => POLICY_RULESET_VERSION,
|
|
8243
|
+
auditContent: () => auditContent,
|
|
8242
8244
|
checkRepoPolicy: () => checkRepoPolicy
|
|
8243
8245
|
});
|
|
8244
8246
|
async function fetchContentsFile(fetchImpl, repoFullName, path) {
|
|
@@ -8260,23 +8262,50 @@ async function fetchContentsFile(fetchImpl, repoFullName, path) {
|
|
|
8260
8262
|
return { ok: false, missing: false, content: null };
|
|
8261
8263
|
}
|
|
8262
8264
|
}
|
|
8263
|
-
function
|
|
8264
|
-
|
|
8265
|
+
function classifyLine(line) {
|
|
8266
|
+
if (PROHIBITED_PATTERNS.some((re) => re.test(line))) return "prohibited";
|
|
8267
|
+
if (DISCLOSURE_PATTERNS.some((re) => re.test(line))) return "disclosure-required";
|
|
8268
|
+
if (AI_SIGNAL_PATTERNS.some((p) => p.re.test(line))) return "ai-mentioned";
|
|
8269
|
+
return null;
|
|
8270
|
+
}
|
|
8271
|
+
function sanitizeExcerpt(text) {
|
|
8272
|
+
return text.replace(/[\x00-\x08\x0B-\x1F\x7F-\x9F]/g, "");
|
|
8273
|
+
}
|
|
8274
|
+
function excerptAround(lines, i) {
|
|
8275
|
+
const start = Math.max(0, i - 2);
|
|
8276
|
+
const end = Math.min(lines.length, i + 3);
|
|
8277
|
+
return sanitizeExcerpt(lines.slice(start, end).join("\n"));
|
|
8278
|
+
}
|
|
8279
|
+
function auditContent(files) {
|
|
8265
8280
|
const hits = [];
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
const
|
|
8270
|
-
|
|
8271
|
-
|
|
8281
|
+
const requirements = [];
|
|
8282
|
+
const seenKinds = /* @__PURE__ */ new Set();
|
|
8283
|
+
for (const { file, content } of files) {
|
|
8284
|
+
const lines = content.split("\n");
|
|
8285
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8286
|
+
const rule = classifyLine(lines[i]);
|
|
8287
|
+
if (rule) hits.push({ file, excerpt: excerptAround(lines, i), rule });
|
|
8288
|
+
for (const { kind, re } of REQUIREMENT_PATTERNS) {
|
|
8289
|
+
if (seenKinds.has(kind) || !re.test(lines[i])) continue;
|
|
8290
|
+
seenKinds.add(kind);
|
|
8291
|
+
requirements.push({ kind, file, excerpt: excerptAround(lines, i) });
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8272
8294
|
}
|
|
8273
|
-
|
|
8295
|
+
let verdict = "clean";
|
|
8296
|
+
for (const h of hits) {
|
|
8297
|
+
if (verdict === "clean" || VERDICT_SEVERITY[h.rule] > VERDICT_SEVERITY[verdict]) {
|
|
8298
|
+
verdict = h.rule;
|
|
8299
|
+
}
|
|
8300
|
+
}
|
|
8301
|
+
const assignment = seenKinds.has("take-bot") ? "take-bot" : seenKinds.has("assignment-required") ? "required" : "none";
|
|
8302
|
+
return { hits, requirements, verdict, assignment };
|
|
8274
8303
|
}
|
|
8275
8304
|
async function checkRepoPolicy(repoFullName, opts = {}) {
|
|
8276
8305
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
8277
8306
|
let requestsUsed = 0;
|
|
8278
8307
|
let hadError = false;
|
|
8279
|
-
const
|
|
8308
|
+
const files = [];
|
|
8280
8309
|
outer: for (const group of CANDIDATE_GROUPS) {
|
|
8281
8310
|
for (const path of group) {
|
|
8282
8311
|
if (requestsUsed >= MAX_REQUESTS) break outer;
|
|
@@ -8287,21 +8316,27 @@ async function checkRepoPolicy(repoFullName, opts = {}) {
|
|
|
8287
8316
|
continue;
|
|
8288
8317
|
}
|
|
8289
8318
|
if (outcome.missing) continue;
|
|
8290
|
-
if (outcome.content)
|
|
8319
|
+
if (outcome.content) files.push({ file: path, content: outcome.content });
|
|
8291
8320
|
break;
|
|
8292
8321
|
}
|
|
8293
8322
|
}
|
|
8294
|
-
|
|
8295
|
-
if (
|
|
8296
|
-
|
|
8323
|
+
const { hits, requirements, verdict, assignment } = auditContent(files);
|
|
8324
|
+
if (hits.length > 0) {
|
|
8325
|
+
return { status: "flagged", verdict, hits, requirements, assignment, rulesetVersion: POLICY_RULESET_VERSION };
|
|
8326
|
+
}
|
|
8327
|
+
if (hadError) {
|
|
8328
|
+
return { status: "unavailable", verdict: "unavailable", hits: [], requirements, assignment, rulesetVersion: POLICY_RULESET_VERSION };
|
|
8329
|
+
}
|
|
8330
|
+
return { status: "clean", verdict: "clean", hits: [], requirements, assignment, rulesetVersion: POLICY_RULESET_VERSION };
|
|
8297
8331
|
}
|
|
8298
|
-
var GH_API, GH_HEADERS, MAX_REQUESTS, AI_SIGNAL_PATTERNS, CANDIDATE_GROUPS;
|
|
8332
|
+
var GH_API, GH_HEADERS, MAX_REQUESTS, POLICY_RULESET_VERSION, AI_SIGNAL_PATTERNS, AI_TERM, PROHIBITED_PATTERNS, DISCLOSURE_PATTERNS, REQUIREMENT_PATTERNS, CANDIDATE_GROUPS, VERDICT_SEVERITY;
|
|
8299
8333
|
var init_repo_policy = __esm({
|
|
8300
8334
|
"src/repo-policy.ts"() {
|
|
8301
8335
|
"use strict";
|
|
8302
8336
|
GH_API = "https://api.github.com";
|
|
8303
8337
|
GH_HEADERS = { "User-Agent": "terminalhire-claim", Accept: "application/vnd.github+json" };
|
|
8304
8338
|
MAX_REQUESTS = 4;
|
|
8339
|
+
POLICY_RULESET_VERSION = 2;
|
|
8305
8340
|
AI_SIGNAL_PATTERNS = [
|
|
8306
8341
|
{ label: "AI", re: /\bAI\b/i },
|
|
8307
8342
|
{ label: "artificial intelligence", re: /artificial intelligence/i },
|
|
@@ -8313,11 +8348,47 @@ var init_repo_policy = __esm({
|
|
|
8313
8348
|
{ label: "generative", re: /\bgenerative\b/i },
|
|
8314
8349
|
{ label: "machine-generated", re: /machine[\s-]generated/i }
|
|
8315
8350
|
];
|
|
8351
|
+
AI_TERM = "(?:ai|llms?|generative(?:\\s+ai)?|artificial intelligence|language models?|copilot|chatgpt|claude|machine[\\s-]generated)";
|
|
8352
|
+
PROHIBITED_PATTERNS = [
|
|
8353
|
+
new RegExp(`prohibit\\w*[^.\\n]{0,60}\\b${AI_TERM}`, "i"),
|
|
8354
|
+
/did not write the code yourself/i,
|
|
8355
|
+
new RegExp(`(?:not|never|don'?t|won'?t)\\s+accept\\w*[^.\\n]{0,60}\\b${AI_TERM}`, "i"),
|
|
8356
|
+
new RegExp(`\\bno\\s+${AI_TERM}[^.\\n]{0,40}\\b(?:prs?|pull requests?|contributions?|code|patch(?:es)?|commits?|submissions?)\\b`, "i"),
|
|
8357
|
+
new RegExp(
|
|
8358
|
+
`\\b${AI_TERM}[^.\\n]{0,60}\\b(?:is|are|will be)\\s+(?:\\w+\\s+)?(?:not accepted|not allowed|banned|rejected|removed|closed|reverted|prohibited|forbidden)`,
|
|
8359
|
+
"i"
|
|
8360
|
+
)
|
|
8361
|
+
];
|
|
8362
|
+
DISCLOSURE_PATTERNS = [
|
|
8363
|
+
new RegExp(`disclos\\w+[^.\\n]{0,60}\\b${AI_TERM}`, "i"),
|
|
8364
|
+
new RegExp(`\\b${AI_TERM}[^.\\n]{0,60}disclos`, "i"),
|
|
8365
|
+
new RegExp(`\\b${AI_TERM}[\\s-]assist\\w*[^.\\n]{0,60}\\b(?:must|should|required?)\\b`, "i"),
|
|
8366
|
+
// PR-template checkbox, e.g. "- [ ] I used AI tools and have reviewed the output"
|
|
8367
|
+
new RegExp(`\\[ \\][^\\n]{0,80}\\b${AI_TERM}`, "i")
|
|
8368
|
+
];
|
|
8369
|
+
REQUIREMENT_PATTERNS = [
|
|
8370
|
+
// `/take` bot first: its docs usually also say "assign", and the bot is the
|
|
8371
|
+
// more specific expectation (post exactly `/take`, not a prose request).
|
|
8372
|
+
{ kind: "take-bot", re: /(?:^|[\s`"'(])\/take\b/m },
|
|
8373
|
+
{ kind: "assignment-required", re: /(?:request|ask|wait)[^.\n]{0,40}\bassign/i },
|
|
8374
|
+
{ kind: "assignment-required", re: /\bassigned before\b/i },
|
|
8375
|
+
{ kind: "assignment-required", re: /\bself[\s-]assign/i },
|
|
8376
|
+
{ kind: "assignment-required", re: /do not (?:open|submit)[^.\n]{0,40}\b(?:prs?|pull requests?)\b/i },
|
|
8377
|
+
{ kind: "cla-required", re: /\bCLA\b/ },
|
|
8378
|
+
{ kind: "cla-required", re: /contributor licen[cs]e agreement/i },
|
|
8379
|
+
{ kind: "discussion-first", re: /open an issue (?:first|before)/i },
|
|
8380
|
+
{ kind: "discussion-first", re: /discuss\w*[^.\n]{0,40}\bbefore\b/i }
|
|
8381
|
+
];
|
|
8316
8382
|
CANDIDATE_GROUPS = [
|
|
8317
8383
|
["CONTRIBUTING.md", ".github/CONTRIBUTING.md", "docs/CONTRIBUTING.md"],
|
|
8318
|
-
[".
|
|
8319
|
-
["
|
|
8384
|
+
["AGENTS.md", "AGENTS.MD"],
|
|
8385
|
+
[".github/PULL_REQUEST_TEMPLATE.md", "PULL_REQUEST_TEMPLATE.md"]
|
|
8320
8386
|
];
|
|
8387
|
+
VERDICT_SEVERITY = {
|
|
8388
|
+
"ai-mentioned": 1,
|
|
8389
|
+
"disclosure-required": 2,
|
|
8390
|
+
prohibited: 3
|
|
8391
|
+
};
|
|
8321
8392
|
}
|
|
8322
8393
|
});
|
|
8323
8394
|
|
|
@@ -9010,17 +9081,38 @@ function buildSubmitBody(issueNumber, opts = {}) {
|
|
|
9010
9081
|
return `${closesLine}${main}${AI_DISCLOSURE_NOTE}`;
|
|
9011
9082
|
}
|
|
9012
9083
|
function printPolicySection(policy) {
|
|
9013
|
-
|
|
9084
|
+
const verdict = policy.verdict ?? (policy.status === "flagged" ? "ai-mentioned" : policy.status);
|
|
9085
|
+
if (verdict === "prohibited") {
|
|
9086
|
+
console.log("\n POLICY: \u2717 this repo PROHIBITS AI-generated contributions \u2014 READ BEFORE CLAIMING:");
|
|
9087
|
+
} else if (verdict === "disclosure-required") {
|
|
9088
|
+
console.log("\n POLICY: \u26A0 this repo requires disclosing AI assistance \u2014 READ BEFORE WORKING:");
|
|
9089
|
+
} else if (verdict === "ai-mentioned") {
|
|
9014
9090
|
console.log("\n POLICY: \u26A0 possible AI-assistance policy language found \u2014 READ BEFORE WORKING:");
|
|
9015
|
-
for (const hit of policy.hits) {
|
|
9016
|
-
console.log(` [${hit.file}]`);
|
|
9017
|
-
for (const line of hit.excerpt.split("\n")) console.log(` ${line}`);
|
|
9018
|
-
}
|
|
9019
9091
|
} else if (policy.status === "unavailable") {
|
|
9020
9092
|
console.log("\n POLICY: could not read this repo's CONTRIBUTING/PR-template/AGENTS docs (rate-limited or unreachable) \u2014 read them yourself before working.");
|
|
9021
9093
|
} else {
|
|
9022
9094
|
console.log(" policy: no AI-assistance policy language detected in CONTRIBUTING/PR-template/AGENTS docs");
|
|
9023
9095
|
}
|
|
9096
|
+
for (const hit of policy.hits ?? []) {
|
|
9097
|
+
console.log(` [${hit.file}]`);
|
|
9098
|
+
for (const line of hit.excerpt.split("\n")) console.log(` ${line}`);
|
|
9099
|
+
}
|
|
9100
|
+
const requirements = policy.requirements ?? [];
|
|
9101
|
+
if (requirements.length > 0) {
|
|
9102
|
+
console.log("\n REQUIREMENTS: this repo sets contribution expectations \u2014 in its own words:");
|
|
9103
|
+
for (const req of requirements) {
|
|
9104
|
+
console.log(` ${req.kind} [${req.file}]`);
|
|
9105
|
+
for (const line of req.excerpt.split("\n")) console.log(` ${line}`);
|
|
9106
|
+
}
|
|
9107
|
+
}
|
|
9108
|
+
const assignment = policy.assignment ?? "none";
|
|
9109
|
+
if (assignment === "take-bot") {
|
|
9110
|
+
console.log(" assignment: repo self-assigns via a /take comment \u2014 `claim start` will post `/take` on the issue");
|
|
9111
|
+
} else if (assignment === "required") {
|
|
9112
|
+
console.log(" assignment: repo expects you to request assignment \u2014 `claim start` will post a request comment on the issue");
|
|
9113
|
+
} else {
|
|
9114
|
+
console.log(" assignment: no assignment expectation found in repo docs \u2014 `claim start` will not comment (pass --assign to request anyway)");
|
|
9115
|
+
}
|
|
9024
9116
|
}
|
|
9025
9117
|
var pExecFile = promisify2(execFile2);
|
|
9026
9118
|
async function sh(cmd, args, opts = {}) {
|
|
@@ -9291,9 +9383,9 @@ async function fetchIssue(repoFullName, issueNumber) {
|
|
|
9291
9383
|
}
|
|
9292
9384
|
var ASSIGNMENT_MARKER = "<!-- terminalhire:assignment-request -->";
|
|
9293
9385
|
var TAKE_BOT_REPOS = /* @__PURE__ */ new Set(["paradedb/paradedb"]);
|
|
9294
|
-
function buildAssignmentComment(repoFullName) {
|
|
9295
|
-
const usesTakeBot = TAKE_BOT_REPOS.has(repoFullName.toLowerCase());
|
|
9296
|
-
const body = usesTakeBot ? "/take" : `I'd like to work on this
|
|
9386
|
+
function buildAssignmentComment(repoFullName, opts = {}) {
|
|
9387
|
+
const usesTakeBot = TAKE_BOT_REPOS.has(repoFullName.toLowerCase()) || Boolean(opts.takeBot);
|
|
9388
|
+
const body = usesTakeBot ? "/take" : `Hi! I'd like to work on this issue. Could you assign it to me? Thanks!
|
|
9297
9389
|
|
|
9298
9390
|
${ASSIGNMENT_MARKER}`;
|
|
9299
9391
|
return { usesTakeBot, body };
|
|
@@ -9314,6 +9406,11 @@ async function hasPriorAssignmentRequest(repoFullName, issueNumber, login) {
|
|
|
9314
9406
|
return false;
|
|
9315
9407
|
}
|
|
9316
9408
|
}
|
|
9409
|
+
function shouldRequestAssignment(claim, flags = {}) {
|
|
9410
|
+
const expectation = claim && claim.policy ? claim.policy.assignment : void 0;
|
|
9411
|
+
const post = Boolean(flags.assign) || expectation === void 0 || expectation === "required" || expectation === "take-bot";
|
|
9412
|
+
return { post, takeBot: expectation === "take-bot" };
|
|
9413
|
+
}
|
|
9317
9414
|
async function requestIssueAssignment(claim, flags = {}, ghUser) {
|
|
9318
9415
|
if (flags["no-assign"]) {
|
|
9319
9416
|
console.log(" (--no-assign \u2014 skipping the assignment request; request it manually before working)");
|
|
@@ -9322,6 +9419,11 @@ async function requestIssueAssignment(claim, flags = {}, ghUser) {
|
|
|
9322
9419
|
const parsed = parseGitHubUrl(claim.issueUrl);
|
|
9323
9420
|
if (!parsed) return;
|
|
9324
9421
|
const { repoFullName, number } = parsed;
|
|
9422
|
+
const expectation = shouldRequestAssignment(claim, flags);
|
|
9423
|
+
if (!expectation.post) {
|
|
9424
|
+
console.log(" (no assignment expectation found in repo docs \u2014 pass --assign to request assignment anyway)");
|
|
9425
|
+
return;
|
|
9426
|
+
}
|
|
9325
9427
|
let login = ghUser;
|
|
9326
9428
|
if (!login) {
|
|
9327
9429
|
try {
|
|
@@ -9336,11 +9438,21 @@ async function requestIssueAssignment(claim, flags = {}, ghUser) {
|
|
|
9336
9438
|
console.log(` \u2713 Already assigned to @${login} on ${repoFullName}#${number}.`);
|
|
9337
9439
|
return;
|
|
9338
9440
|
}
|
|
9339
|
-
const { usesTakeBot, body } = buildAssignmentComment(repoFullName);
|
|
9441
|
+
const { usesTakeBot, body } = buildAssignmentComment(repoFullName, { takeBot: expectation.takeBot });
|
|
9340
9442
|
if (!usesTakeBot && await hasPriorAssignmentRequest(repoFullName, number, login)) {
|
|
9341
9443
|
console.log(` \u2713 Assignment already requested on ${repoFullName}#${number}.`);
|
|
9342
9444
|
return;
|
|
9343
9445
|
}
|
|
9446
|
+
if (process.stdin.isTTY) {
|
|
9447
|
+
console.log(`
|
|
9448
|
+
About to comment on ${repoFullName}#${number} as @${login}:`);
|
|
9449
|
+
for (const line of body.split("\n")) console.log(` ${line}`);
|
|
9450
|
+
const go = await confirm(" Post it? (y/N) ");
|
|
9451
|
+
if (!go) {
|
|
9452
|
+
console.log(" (skipped \u2014 request assignment manually before working)");
|
|
9453
|
+
return;
|
|
9454
|
+
}
|
|
9455
|
+
}
|
|
9344
9456
|
try {
|
|
9345
9457
|
await sh("gh", ["issue", "comment", String(number), "--repo", repoFullName, "--body", body]);
|
|
9346
9458
|
console.log(
|
|
@@ -9473,7 +9585,7 @@ function fmtContestedWarning(b) {
|
|
|
9473
9585
|
async function cmdRecord(arg, flags = {}) {
|
|
9474
9586
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
9475
9587
|
if (!arg) {
|
|
9476
|
-
console.error("Usage: terminalhire claim record <bountyId|issueUrl> [--ack-policy] [--ack-contested]");
|
|
9588
|
+
console.error("Usage: terminalhire claim record <bountyId|issueUrl> [--ack-policy] [--ack-policy-prohibited] [--ack-contested]");
|
|
9477
9589
|
console.error(" Run `terminalhire bounties` first to populate the local index cache,");
|
|
9478
9590
|
console.error(" then pass the id shown in its output \u2014 or pass a GitHub issue URL directly.");
|
|
9479
9591
|
process.exit(1);
|
|
@@ -9511,8 +9623,25 @@ ${contestedWarning}`);
|
|
|
9511
9623
|
const policy = await checkRepoPolicy2(b.repoFullName);
|
|
9512
9624
|
printPolicySection(policy);
|
|
9513
9625
|
let ackedAt = null;
|
|
9514
|
-
if (policy.
|
|
9515
|
-
|
|
9626
|
+
if (policy.verdict === "prohibited") {
|
|
9627
|
+
let acked = Boolean(flags["ack-policy-prohibited"]);
|
|
9628
|
+
if (!acked && process.stdin.isTTY) {
|
|
9629
|
+
acked = await confirm(
|
|
9630
|
+
"\n This repo PROHIBITS AI-generated contributions \u2014 continuing means everything you submit must be hand-written by you. Continue? (y/N) "
|
|
9631
|
+
);
|
|
9632
|
+
}
|
|
9633
|
+
if (!acked) {
|
|
9634
|
+
console.error(
|
|
9635
|
+
`
|
|
9636
|
+
terminalhire claim: refusing to record \u2014 ${b.repoFullName} prohibits AI-generated contributions.
|
|
9637
|
+
Read the excerpts above. If you will hand-write the work yourself, re-run with
|
|
9638
|
+
--ack-policy-prohibited (or confirm interactively). --ack-policy is not enough here.`
|
|
9639
|
+
);
|
|
9640
|
+
process.exit(1);
|
|
9641
|
+
}
|
|
9642
|
+
ackedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9643
|
+
} else if (policy.status === "flagged" || policy.status === "unavailable") {
|
|
9644
|
+
const reason = policy.status === "flagged" ? "This repo has AI-assistance policy language" : "This repo's contribution policy could not be checked";
|
|
9516
9645
|
let acked = Boolean(flags["ack-policy"]);
|
|
9517
9646
|
if (!acked && process.stdin.isTTY) {
|
|
9518
9647
|
acked = await confirm(`
|
|
@@ -9542,7 +9671,18 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
|
|
|
9542
9671
|
amountUSD: b.amountUSD,
|
|
9543
9672
|
openPRsAtClaim: b.openPRs,
|
|
9544
9673
|
kind,
|
|
9545
|
-
|
|
9674
|
+
// Persist the full audit verdict (not just the coarse status) so `claim
|
|
9675
|
+
// start` can drive the assignment decision from it and `claim audit` can
|
|
9676
|
+
// show what ruleset the dev acknowledged. Excerpts are NOT persisted —
|
|
9677
|
+
// the record stores the conclusion; the docs stay the source of truth.
|
|
9678
|
+
policy: {
|
|
9679
|
+
status: policy.status,
|
|
9680
|
+
verdict: policy.verdict,
|
|
9681
|
+
assignment: policy.assignment,
|
|
9682
|
+
requirements: (policy.requirements ?? []).map((r) => r.kind),
|
|
9683
|
+
rulesetVersion: policy.rulesetVersion,
|
|
9684
|
+
ackedAt
|
|
9685
|
+
}
|
|
9546
9686
|
});
|
|
9547
9687
|
} catch (err) {
|
|
9548
9688
|
console.error(`terminalhire claim: ${err.message ?? err}`);
|
|
@@ -9604,7 +9744,14 @@ async function cmdPreview(arg, { json } = {}) {
|
|
|
9604
9744
|
openPRs: b.openPRs,
|
|
9605
9745
|
assignees: b.assignees,
|
|
9606
9746
|
contested: isContested(b),
|
|
9607
|
-
policy: {
|
|
9747
|
+
policy: {
|
|
9748
|
+
status: policy.status,
|
|
9749
|
+
verdict: policy.verdict,
|
|
9750
|
+
assignment: policy.assignment,
|
|
9751
|
+
rulesetVersion: policy.rulesetVersion,
|
|
9752
|
+
hits: policy.hits,
|
|
9753
|
+
requirements: policy.requirements
|
|
9754
|
+
}
|
|
9608
9755
|
}) + "\n"
|
|
9609
9756
|
);
|
|
9610
9757
|
return;
|
|
@@ -10736,6 +10883,7 @@ export {
|
|
|
10736
10883
|
run,
|
|
10737
10884
|
selectCompetingPrs,
|
|
10738
10885
|
selectPushRemote,
|
|
10886
|
+
shouldRequestAssignment,
|
|
10739
10887
|
startBranchFor,
|
|
10740
10888
|
workDirFor
|
|
10741
10889
|
};
|